{
 "cells": [
  {
   "cell_type": "code",
   "metadata": {
    "colab": {
     "base_uri": "https://localhost:8080/"
    },
    "id": "WKUPkA0TJzbW",
    "outputId": "6d5e2dc2-b311-4925-9fcf-23490499c77a",
    "ExecuteTime": {
     "end_time": "2025-02-06T08:59:36.027176Z",
     "start_time": "2025-02-06T08:59:31.565680Z"
    }
   },
   "source": [
    "import matplotlib as mpl\n",
    "import matplotlib.pyplot as plt\n",
    "%matplotlib inline\n",
    "import numpy as np\n",
    "import sklearn\n",
    "import pandas as pd\n",
    "import os\n",
    "import sys\n",
    "import time\n",
    "from tqdm.auto import tqdm\n",
    "import torch\n",
    "import torch.nn as nn\n",
    "import torch.nn.functional as F\n",
    "\n",
    "print(sys.version_info)\n",
    "for module in mpl, np, pd, sklearn, torch:\n",
    "    print(module.__name__, module.__version__)\n",
    "\n",
    "device = torch.device(\"cuda:0\") if torch.cuda.is_available() else torch.device(\"cpu\")\n",
    "print(device)\n",
    "\n",
    "seed = 42\n",
    "torch.manual_seed(seed)\n",
    "torch.cuda.manual_seed_all(seed)\n",
    "np.random.seed(seed)\n"
   ],
   "outputs": [
    {
     "output_type": "stream",
     "name": "stdout",
     "text": [
      "sys.version_info(major=3, minor=11, micro=11, releaselevel='final', serial=0)\n",
      "matplotlib 3.10.0\n",
      "numpy 1.26.4\n",
      "pandas 2.2.2\n",
      "sklearn 1.6.1\n",
      "torch 2.5.1+cu124\n",
      "cuda:0\n"
     ]
    }
   ],
   "execution_count": 2
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "id": "pSNcMyqvJzbY"
   },
   "source": [
    "## 数据加载"
   ]
  },
  {
   "cell_type": "code",
   "metadata": {
    "colab": {
     "base_uri": "https://localhost:8080/"
    },
    "id": "Dm1DrSCLJzbZ",
    "outputId": "19a78cc4-50bb-48f1-96c1-53e391bcf19a",
    "ExecuteTime": {
     "end_time": "2025-02-06T08:59:46.408185Z",
     "start_time": "2025-02-06T08:59:46.310844Z"
    }
   },
   "source": [
    "import unicodedata\n",
    "import re\n",
    "from sklearn.model_selection import train_test_split\n",
    "\n",
    "# 因为西班牙语有一些是特殊字符，所以我们需要unicode转ascii，\n",
    "# 这样值变小了，因为unicode太大\n",
    "def unicode_to_ascii(s):\n",
    "    # NFD是转换方法，把每一个字节拆开，Mn是西班牙重音，所以去除\n",
    "    return ''.join(c for c in unicodedata.normalize('NFD', s) if unicodedata.category(c) != 'Mn')\n",
    "\n",
    "#下面我们找个样本测试一下\n",
    "# 加u代表对字符串进行unicode编码\n",
    "en_sentence = u\"May I borrow this book?\"\n",
    "sp_sentence = u\"¿Puedo tomar prestado este libro?\"\n",
    "\n",
    "print(unicode_to_ascii(en_sentence))\n",
    "print(unicode_to_ascii(sp_sentence))"
   ],
   "outputs": [
    {
     "output_type": "stream",
     "name": "stdout",
     "text": [
      "May I borrow this book?\n",
      "¿Puedo tomar prestado este libro?\n"
     ]
    }
   ],
   "execution_count": 3
  },
  {
   "cell_type": "code",
   "source": [
    "def preprocess_sentence(w):\n",
    "    # 变为小写，去掉多余的空格，变成小写，id少一些\n",
    "    w = unicode_to_ascii(w.lower().strip())\n",
    "\n",
    "    # 在单词与跟在其后的标点符号之间插入一个空格\n",
    "    # eg: \"he is a boy.\" => \"he is a boy . \"\n",
    "    # Reference:- https://stackoverflow.com/questions/3645931/python-padding-punctuation-with-white-spaces-keeping-punctuation\n",
    "    w = re.sub(r\"([?.!,¿])\", r\" \\1 \", w)\n",
    "    #因为可能有多余空格，替换为一个空格，所以处理一下\n",
    "    w = re.sub(r'[\" \"]+', \" \", w)\n",
    "    # 除了 (a-z, A-Z, \".\", \"?\", \"!\", \",\")，将所有字符替换为空格\n",
    "    w = re.sub(r\"[^a-zA-Z?.!,¿]+\", \" \", w)\n",
    "    # 去掉开头和结尾的空格\n",
    "    w = w.rstrip().strip()\n",
    "\n",
    "    return w\n",
    "\n",
    "print(preprocess_sentence(en_sentence))\n",
    "print(preprocess_sentence(sp_sentence))\n",
    "print(preprocess_sentence(sp_sentence).encode('utf-8'))  #¿是占用两个字节的"
   ],
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-02-06T09:00:56.140269Z",
     "start_time": "2025-02-06T09:00:56.135543Z"
    },
    "colab": {
     "base_uri": "https://localhost:8080/"
    },
    "id": "SIxvBThETsvp",
    "outputId": "d3e13b8e-cd78-4aea-d62e-c62bc15314b9"
   },
   "outputs": [
    {
     "output_type": "stream",
     "name": "stdout",
     "text": [
      "may i borrow this book ?\n",
      "¿ puedo tomar prestado este libro ?\n",
      "b'\\xc2\\xbf puedo tomar prestado este libro ?'\n"
     ]
    }
   ],
   "execution_count": 4
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "id": "YyJksrNmJzba"
   },
   "source": [
    "Dataset"
   ]
  },
  {
   "cell_type": "code",
   "source": [
    "# zip例子——>把[en,sp]中的en和sp分别打包成元组，然后打包成列表\n",
    "a = [[1,2],[4,5],[7,8]]\n",
    "zipped = list(zip(*a))\n",
    "print(zipped)\n",
    "en,sp = list(zip(*a))\n",
    "print(en)\n",
    "print(sp)"
   ],
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-02-05T05:56:24.619551Z",
     "start_time": "2025-02-05T05:56:24.616Z"
    },
    "id": "TkyZEkI7Tsvr",
    "outputId": "eea0b5de-fc87-4647-9ba8-578758c34019",
    "colab": {
     "base_uri": "https://localhost:8080/"
    }
   },
   "outputs": [
    {
     "output_type": "stream",
     "name": "stdout",
     "text": [
      "[(1, 4, 7), (2, 5, 8)]\n",
      "(1, 4, 7)\n",
      "(2, 5, 8)\n"
     ]
    }
   ],
   "execution_count": 23
  },
  {
   "cell_type": "code",
   "source": [
    "split_index1 = np.random.choice(a=[\"train\", \"test\"], replace=True, p=[0.9, 0.1], size=100)\n",
    "split_index1"
   ],
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-02-05T06:17:55.827674Z",
     "start_time": "2025-02-05T06:17:55.821626Z"
    },
    "id": "W0RAN1quTsvr",
    "outputId": "3c2cda0e-3e59-46d5-ea56-d64a6f036cbb",
    "colab": {
     "base_uri": "https://localhost:8080/"
    }
   },
   "outputs": [
    {
     "output_type": "execute_result",
     "data": {
      "text/plain": [
       "array(['test', 'train', 'train', 'test', 'train', 'train', 'train',\n",
       "       'train', 'test', 'test', 'test', 'train', 'train', 'train',\n",
       "       'train', 'train', 'train', 'train', 'train', 'train', 'test',\n",
       "       'train', 'train', 'train', 'train', 'train', 'train', 'train',\n",
       "       'train', 'test', 'train', 'test', 'train', 'train', 'train',\n",
       "       'train', 'train', 'test', 'train', 'train', 'train', 'train',\n",
       "       'train', 'train', 'train', 'train', 'train', 'train', 'train',\n",
       "       'test', 'train', 'train', 'train', 'train', 'train', 'train',\n",
       "       'train', 'train', 'train', 'train', 'test', 'train', 'train',\n",
       "       'train', 'test', 'test', 'train', 'train', 'train', 'train',\n",
       "       'train', 'train', 'train', 'train', 'train', 'train', 'train',\n",
       "       'train', 'train', 'train', 'train', 'train', 'train', 'train',\n",
       "       'train', 'test', 'train', 'train', 'train', 'train', 'train',\n",
       "       'train', 'train', 'train', 'train', 'test', 'train', 'train',\n",
       "       'train', 'train'], dtype='<U5')"
      ]
     },
     "metadata": {},
     "execution_count": 24
    }
   ],
   "execution_count": 24
  },
  {
   "cell_type": "code",
   "metadata": {
    "id": "-VnoIKhaJzba",
    "ExecuteTime": {
     "end_time": "2025-02-06T09:13:36.558209Z",
     "start_time": "2025-02-06T09:13:36.262818Z"
    }
   },
   "source": [
    "from pathlib import Path\n",
    "from torch.utils.data import Dataset, DataLoader\n",
    "\n",
    "class LangPairDataset(Dataset):\n",
    "    fpath = Path(r\"./spa.txt\") # 数据文件路径\n",
    "    cache_path = Path(r\"./.cache/lang_pair.npy\") # 缓存文件路径\n",
    "    split_index = np.random.choice(a=[\"train\", \"test\"], replace=True, p=[0.9, 0.1], size=118964) # 按照9:1划分训练集和测试集\n",
    "    def __init__(self, mode=\"train\", cache=False):\n",
    "        if cache or not self.cache_path.exists():# 如果没有缓存，或者缓存不存在，就处理一下数据\n",
    "            self.cache_path.parent.mkdir(parents=True, exist_ok=True) # 创建缓存文件夹，如果存在就忽略\n",
    "            with open(self.fpath, \"r\", encoding=\"utf8\") as file:\n",
    "                lines = file.readlines()\n",
    "                lang_pair = [[preprocess_sentence(w) for w in l.split('\\t')]  for l in lines] # 处理数据，变成list((trg, src))的形式\n",
    "                trg, src = zip(*lang_pair) #分离出目标语言和源语言\n",
    "                trg=np.array(trg)  # 转换为numpy数组\n",
    "                src=np.array(src)  # 转换为numpy数组\n",
    "                np.save(self.cache_path, {\"trg\": trg, \"src\": src})  # 保存为npy文件,方便下次直接读取,不用再处理\n",
    "                print(i)\n",
    "        else:\n",
    "            lang_pair = np.load(self.cache_path, allow_pickle=True).item() # 读取npy文件，allow_pickle=True允许读取字典\n",
    "            trg = lang_pair[\"trg\"]\n",
    "            src = lang_pair[\"src\"]\n",
    "\n",
    "        self.trg = trg[self.split_index == mode] # 按照index拿到训练集的 标签语言 --英语\n",
    "        self.src = src[self.split_index == mode] # 按照index拿到训练集的源语言 --西班牙\n",
    "\n",
    "    def __getitem__(self, index):\n",
    "        return self.src[index], self.trg[index]\n",
    "\n",
    "    def __len__(self):\n",
    "        return len(self.src)\n",
    "\n",
    "\n",
    "train_ds = LangPairDataset(\"train\")\n",
    "test_ds = LangPairDataset(\"test\")"
   ],
   "outputs": [],
   "execution_count": 7
  },
  {
   "cell_type": "code",
   "metadata": {
    "colab": {
     "base_uri": "https://localhost:8080/"
    },
    "id": "knue-PUkJzbb",
    "outputId": "8caac3bd-8d2e-47f2-9b89-088a15b0fe82",
    "ExecuteTime": {
     "end_time": "2025-02-05T06:02:34.302517Z",
     "start_time": "2025-02-05T06:02:34.296442Z"
    }
   },
   "source": [
    "print(\"source: {}\\ntarget: {}\".format(*train_ds[-1]))  # *拆包"
   ],
   "outputs": [
    {
     "output_type": "stream",
     "name": "stdout",
     "text": [
      "source: si quieres sonar como un hablante nativo , debes estar dispuesto a practicar diciendo la misma frase una y otra vez de la misma manera en que un musico de banjo practica el mismo fraseo una y otra vez hasta que lo puedan tocar correctamente y en el tiempo esperado .\n",
      "target: if you want to sound like a native speaker , you must be willing to practice saying the same sentence over and over in the same way that banjo players practice the same phrase over and over until they can play it correctly and at the desired tempo .\n"
     ]
    }
   ],
   "execution_count": 33
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "id": "9mzBlPtGJzbb"
   },
   "source": [
    "### Tokenizer\n",
    "\n",
    "这里有两种处理方式，分别对应着 encoder 和 decoder 的 word embedding 是否共享，这里实现不共享的方案。"
   ]
  },
  {
   "cell_type": "code",
   "metadata": {
    "id": "fMSIczSnJzbb",
    "ExecuteTime": {
     "end_time": "2025-02-06T09:13:40.162347Z",
     "start_time": "2025-02-06T09:13:39.627105Z"
    },
    "outputId": "c3bc0e25-7cf3-49d2-85ca-a1c5f02890a8",
    "colab": {
     "base_uri": "https://localhost:8080/"
    }
   },
   "source": [
    "from collections import Counter\n",
    "\n",
    "def get_word_idx(ds, mode=\"src\", threshold=1):\n",
    "    #载入词表，看下词表长度，词表就像英语字典\n",
    "    word2idx = {\n",
    "        \"[PAD]\": 0,     # 填充 token\n",
    "        \"[BOS]\": 1,     # begin of sentence\n",
    "        \"[UNK]\": 2,     # 未知 token\n",
    "        \"[EOS]\": 3,     # end of sentence\n",
    "    }\n",
    "    idx2word = {value: key for key, value in word2idx.items()}\n",
    "\n",
    "    # 如果数据集有很多个G，那是用for循环的，不能' '.join\n",
    "    word_list = \" \".join([pair[0 if mode==\"src\" else 1] for pair in ds]).split()\n",
    "    # print#(type(word_list))  split()返回一个列表，join()返回一个字符串\n",
    "    counter = Counter(word_list) # 统计词频,counter类似字典，key是单词，value是出现次数\n",
    "    print(\"word count:\", len(counter))\n",
    "\n",
    "    index = len(idx2word)\n",
    "    for token, count in counter.items():\n",
    "        if count >= threshold:#出现次数大于阈值的token加入词表\n",
    "            word2idx[token] = index  # 加入词表\n",
    "            idx2word[index] = token  # 加入反向词表\n",
    "            index += 1\n",
    "\n",
    "    return word2idx, idx2word\n",
    "\n",
    "src_word2idx, src_idx2word = get_word_idx(train_ds, \"src\") # 源语言词表\n",
    "trg_word2idx, trg_idx2word = get_word_idx(train_ds, \"trg\") # 目标语言词表"
   ],
   "outputs": [
    {
     "output_type": "stream",
     "name": "stdout",
     "text": [
      "word count: 23768\n",
      "word count: 12517\n"
     ]
    }
   ],
   "execution_count": 8
  },
  {
   "cell_type": "code",
   "metadata": {
    "colab": {
     "base_uri": "https://localhost:8080/"
    },
    "id": "9_IjY_wIJzbb",
    "outputId": "d96b8ec6-eec3-4d2d-c829-49248b9a96c3",
    "ExecuteTime": {
     "end_time": "2025-02-06T09:27:40.709824Z",
     "start_time": "2025-02-06T09:27:40.682552Z"
    }
   },
   "source": [
    "class Tokenizer:\n",
    "    def __init__(self, word2idx, idx2word, max_length=500, pad_idx=0, bos_idx=1, eos_idx=3, unk_idx=2):\n",
    "        self.word2idx = word2idx\n",
    "        self.idx2word = idx2word\n",
    "        self.max_length = max_length\n",
    "        self.pad_idx = pad_idx\n",
    "        self.bos_idx = bos_idx\n",
    "        self.eos_idx = eos_idx\n",
    "        self.unk_idx = unk_idx\n",
    "\n",
    "    def encode(self, text_list, padding_first=False, add_bos=True, add_eos=True, return_mask=False):\n",
    "        \"\"\"如果padding_first == True，则padding加载前面，否则加载后面\n",
    "        return_mask: 是否返回mask(掩码），mask用于指示哪些是padding的，哪些是真实的token\n",
    "        \"\"\"\n",
    "        # 一次处理一批text，所以用list\n",
    "        max_length = min(self.max_length, add_eos + add_bos + max([len(text) for text in text_list]))\n",
    "        indices_list = []\n",
    "        for text in text_list:\n",
    "            # 如果词表中没有这个词，就用unk_idx代替；indices是一个list,里面是每个词的index,也就是一个样本的index\n",
    "            indices = [self.word2idx.get(word, self.unk_idx) for word in text[:max_length - add_bos - add_eos]]\n",
    "            if add_bos:\n",
    "                indices = [self.bos_idx] + indices\n",
    "            if add_eos:\n",
    "                indices = indices + [self.eos_idx]\n",
    "            if padding_first:  # padding加载前面，超参可以调\n",
    "                indices = [self.pad_idx] * (max_length - len(indices)) + indices\n",
    "            else:  # padding加载后面\n",
    "                indices = indices + [self.pad_idx] * (max_length - len(indices))\n",
    "            indices_list.append(indices)\n",
    "        input_ids = torch.tensor(indices_list)  # 转换为tensor\n",
    "        # mask是一个和input_ids一样大小的tensor，0代表token，1代表padding，mask用于去除padding的影响\n",
    "        masks = (input_ids == self.pad_idx).to(dtype=torch.int64)\n",
    "        return input_ids if not return_mask else (input_ids, masks)\n",
    "\n",
    "\n",
    "    def decode(self, indices_list, remove_bos=True, remove_eos=True, remove_pad=True, split=False):\n",
    "        text_list = []\n",
    "        for indices in indices_list:\n",
    "            text = []\n",
    "            for index in indices:\n",
    "                word = self.idx2word.get(index, \"[UNK]\") # 如果词表中没有这个词，就用unk_idx代替\n",
    "                if remove_bos and word == \"[BOS]\":\n",
    "                    continue\n",
    "                if remove_eos and word == \"[EOS]\":#如果到达eos，就结束\n",
    "                    break\n",
    "                if remove_pad and word == \"[PAD]\":#如果到达pad，就结束\n",
    "                    break\n",
    "                text.append(word) # 单词添加到列表中\n",
    "            text_list.append(\" \".join(text) if not split else text) # 把列表中的单词拼接，变为一个句子;若split=True，则返回列表text\n",
    "        return text_list\n",
    "\n",
    "#两个相对于1个toknizer的好处是embedding的参数量减少\n",
    "src_tokenizer = Tokenizer(word2idx=src_word2idx, idx2word=src_idx2word) #源语言tokenizer\n",
    "trg_tokenizer = Tokenizer(word2idx=trg_word2idx, idx2word=trg_idx2word) #目标语言tokenizer\n",
    "\n",
    "# trg_tokenizer.encode([[\"hello\"], [\"hello\", \"world\"]], add_bos=True, add_eos=False,return_mask=True)\n",
    "raw_text = [\"hello world\".split(), \"tokenize text datas with batch\".split(), \"this is a test\".split()]\n",
    "indices,mask = trg_tokenizer.encode(raw_text, padding_first=False, add_bos=True, add_eos=True,return_mask=True)\n",
    "print(\"raw text\"+'-'*10)\n",
    "for raw in raw_text:\n",
    "    print(raw)\n",
    "print(\"indices\"+'-'*10)\n",
    "for index in indices:\n",
    "    print(index)\n",
    "print(\"mask\"+'-'*10)\n",
    "for m in mask:\n",
    "    print(m)"
   ],
   "outputs": [
    {
     "output_type": "stream",
     "name": "stdout",
     "text": [
      "raw text----------\n",
      "['hello', 'world']\n",
      "['tokenize', 'text', 'datas', 'with', 'batch']\n",
      "['this', 'is', 'a', 'test']\n",
      "indices----------\n",
      "tensor([   1,  301, 3224,    3,    0,    0,    0])\n",
      "tensor([   1,    2, 3888,    2,  547,    2,    3])\n",
      "tensor([   1,  124,  238,  110, 1260,    3,    0])\n",
      "mask----------\n",
      "tensor([0, 0, 0, 0, 1, 1, 1])\n",
      "tensor([0, 0, 0, 0, 0, 0, 0])\n",
      "tensor([0, 0, 0, 0, 0, 0, 1])\n"
     ]
    }
   ],
   "execution_count": 9
  },
  {
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-02-05T09:03:57.025642Z",
     "start_time": "2025-02-05T09:03:57.021838Z"
    },
    "id": "LIq0a7p1Tsvt",
    "outputId": "902d2997-b707-4777-e690-9c5706730d31"
   },
   "cell_type": "code",
   "source": [
    "decode_text = trg_tokenizer.decode(indices.tolist(), remove_bos=False, remove_eos=False, remove_pad=False, split = False)\n",
    "\n",
    "print(\"decode text\"+'-'*10)\n",
    "for decode in decode_text:\n",
    "    print(decode)"
   ],
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "decode text----------\n",
      "[BOS] hello world [EOS] [PAD] [PAD] [PAD]\n",
      "[BOS] [UNK] text [UNK] with [UNK] [EOS]\n",
      "[BOS] this is a test [EOS] [PAD]\n"
     ]
    }
   ],
   "execution_count": null
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "id": "S8BDjaa1Jzbc"
   },
   "source": [
    "### DataLoader"
   ]
  },
  {
   "cell_type": "code",
   "metadata": {
    "id": "sPwlGzn8Jzbc",
    "ExecuteTime": {
     "end_time": "2025-02-06T10:19:00.190981Z",
     "start_time": "2025-02-06T10:19:00.186675Z"
    }
   },
   "source": [
    "def collate_fct(batch):\n",
    "    src_words = [pair[0].split() for pair in batch]\n",
    "    trg_words = [pair[1].split() for pair in batch]\n",
    "\n",
    "    # [PAD] [BOS] src [EOS]\n",
    "    encoder_inputs, encoder_inputs_mask = src_tokenizer.encode(\n",
    "        src_words, padding_first=True, add_bos=True, add_eos=True, return_mask=True\n",
    "        )\n",
    "\n",
    "    # [BOS] trg [PAD]\n",
    "    decoder_inputs = trg_tokenizer.encode(\n",
    "        trg_words, padding_first=False, add_bos=True, add_eos=False, return_mask=False,\n",
    "        )\n",
    "\n",
    "    # trg [EOS] [PAD]，要mask因为用decode输出计算损失，要把padding排除在外\n",
    "    decoder_labels, decoder_labels_mask = trg_tokenizer.encode(\n",
    "        trg_words, padding_first=False, add_bos=False, add_eos=True, return_mask=True\n",
    "        )\n",
    "\n",
    "    return {\n",
    "        \"encoder_inputs\": encoder_inputs.to(device=device),\n",
    "        \"encoder_inputs_mask\": encoder_inputs_mask.to(device=device),\n",
    "        \"decoder_inputs\": decoder_inputs.to(device=device),\n",
    "        \"decoder_labels\": decoder_labels.to(device=device),\n",
    "        \"decoder_labels_mask\": decoder_labels_mask.to(device=device),\n",
    "    } #当返回的数据较多时，用dict返回比较合理\n"
   ],
   "outputs": [],
   "execution_count": 10
  },
  {
   "cell_type": "code",
   "metadata": {
    "colab": {
     "base_uri": "https://localhost:8080/"
    },
    "id": "_JsuutYAJzbc",
    "outputId": "fd68e596-ed01-4bae-d731-c27f5a758a6c",
    "ExecuteTime": {
     "end_time": "2025-02-05T08:55:53.990367Z",
     "start_time": "2025-02-05T08:55:53.970282Z"
    }
   },
   "source": [
    "sample_dl = DataLoader(train_ds, batch_size=2, shuffle=True, collate_fn=collate_fct)\n",
    "\n",
    "for batch in sample_dl:\n",
    "    for key, value in batch.items():\n",
    "        print(key)\n",
    "        print(value)\n",
    "    break"
   ],
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "encoder_inputs\n",
      "tensor([[   0,    0,    1,   12,  193,  194,  520,  250, 8232,   14,    3],\n",
      "        [   1,   12, 2015, 1188,  194,  380,  120,  157, 7918,   14,    3]])\n",
      "encoder_inputs_mask\n",
      "tensor([[1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0],\n",
      "        [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]])\n",
      "decoder_inputs\n",
      "tensor([[   1,  102,  339,   89,   93, 5974,   10,    0,    0,    0],\n",
      "        [   1,  339,   89, 2478,  235,  738, 1526,  236, 4189,   10]])\n",
      "decoder_labels\n",
      "tensor([[ 102,  339,   89,   93, 5974,   10,    3,    0,    0,    0],\n",
      "        [ 339,   89, 2478,  235,  738, 1526,  236, 4189,   10,    3]])\n",
      "decoder_labels_mask\n",
      "tensor([[0, 0, 0, 0, 0, 0, 0, 1, 1, 1],\n",
      "        [0, 0, 0, 0, 0, 0, 0, 0, 0, 0]])\n"
     ]
    }
   ],
   "execution_count": null
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "id": "K9JaKLR7Jzbc"
   },
   "source": [
    "## 定义模型"
   ]
  },
  {
   "metadata": {
    "id": "x0mPrq9KTsvu"
   },
   "cell_type": "markdown",
   "source": [
    "### Encoder"
   ]
  },
  {
   "cell_type": "code",
   "metadata": {
    "id": "CGxzT605Jzbd",
    "ExecuteTime": {
     "end_time": "2025-02-06T10:22:37.888443Z",
     "start_time": "2025-02-06T10:22:37.884622Z"
    }
   },
   "source": [
    "class Encoder(nn.Module):\n",
    "    def __init__(\n",
    "        self,\n",
    "        vocab_size,\n",
    "        embedding_dim=256,\n",
    "        hidden_dim=1024,\n",
    "        num_layers=1,\n",
    "        ):\n",
    "        super().__init__()\n",
    "        self.embedding = nn.Embedding(vocab_size, embedding_dim)\n",
    "        self.gru = nn.GRU(embedding_dim, hidden_dim, num_layers=num_layers, batch_first=True)\n",
    "\n",
    "    def forward(self, encoder_inputs):\n",
    "        # encoder_inputs.shape = [batch size, sequence length]\n",
    "        # bs, seq_len = encoder_inputs.shape\n",
    "        embeds = self.embedding(encoder_inputs)\n",
    "        # print(f'embeds.shape: {embeds.shape}')  # [batch size, sequence length. embedding_dim]\n",
    "        # embeds.shape = [batch size, sequence length, embedding_dim]->[batch size, sequence length, hidden_dim]\n",
    "        seq_output, hidden = self.gru(embeds)\n",
    "        # seq_output.shape = [batch size, sequence length, hidden_dim]，hidden.shape [ num_layers, batch size, hidden_dim]\n",
    "        return seq_output, hidden"
   ],
   "outputs": [],
   "execution_count": 11
  },
  {
   "cell_type": "code",
   "source": [
    "# 把上面的Encoder写一个例子，看看输出的shape\n",
    "encoder = Encoder(vocab_size=100, embedding_dim=256, hidden_dim=1024, num_layers=4)\n",
    "encoder_inputs = torch.randint(0, 100, (2, 50))\n",
    "print(encoder_inputs.shape)\n",
    "encoder_outputs, hidden = encoder(encoder_inputs)\n",
    "print(encoder_outputs.shape)\n",
    "print(hidden.shape)\n",
    "print(encoder_outputs[:,-1,:])\n",
    "print(hidden[-1,:,:]) # 取最后一层的hidden"
   ],
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-02-05T09:19:31.392360Z",
     "start_time": "2025-02-05T09:19:31.251081Z"
    },
    "id": "cqDWrggOTsvv",
    "outputId": "c5dac5d2-1cff-4df0-c292-50f4e8519b8e"
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "torch.Size([2, 50])\n",
      "embeds.shape: torch.Size([2, 50, 256])\n",
      "torch.Size([2, 50, 1024])\n",
      "torch.Size([4, 2, 1024])\n",
      "tensor([[ 0.0420, -0.0200, -0.0195,  ...,  0.0126,  0.0096, -0.0320],\n",
      "        [-0.0083, -0.0272,  0.0138,  ...,  0.0151,  0.0372, -0.0115]],\n",
      "       grad_fn=<SliceBackward0>)\n",
      "tensor([[ 0.0420, -0.0200, -0.0195,  ...,  0.0126,  0.0096, -0.0320],\n",
      "        [-0.0083, -0.0272,  0.0138,  ...,  0.0151,  0.0372, -0.0115]],\n",
      "       grad_fn=<SliceBackward0>)\n"
     ]
    }
   ],
   "execution_count": null
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "outputs": [
    {
     "data": {
      "text/plain": "torch.Size([2, 1, 1024])"
     },
     "execution_count": 13,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "query1 = torch.randn(2, 1024)\n",
    "query1.unsqueeze(1).shape #增加维度"
   ],
   "metadata": {
    "ExecuteTime": {
     "end_time": "2024-05-06T01:57:05.054653Z",
     "start_time": "2024-05-06T01:57:04.982694100Z"
    },
    "id": "erhDGSIUTsvv",
    "outputId": "70b5776c-a0fa-49ec-bf68-87dc7313d2b1"
   }
  },
  {
   "metadata": {
    "id": "v14trvi7Tsvv"
   },
   "cell_type": "markdown",
   "source": [
    "## BahdanauAttention"
   ]
  },
  {
   "metadata": {
    "id": "86uFWl-ETsvw"
   },
   "cell_type": "markdown",
   "source": [
    "### 公式\n",
    "score = FC(tanh(FC(EO) + FC(H)))—— FC(EO)的FC是Wk，FC(H)的FC是Wq，最外面的FC是V\n",
    "\n",
    "attention_weights = softmax(score, axis = 1) --就变为了权重\n",
    "\n",
    "context = sum(attention_weights * EO, axis = 1) EO 是矩阵，attention_weights 是向量，context 是向量\n",
    "\n",
    "final_input = concat(context, embed(x)) x 和 context 拼接在一起"
   ]
  },
  {
   "cell_type": "code",
   "metadata": {
    "id": "pTQ6Mz1OJzbd",
    "ExecuteTime": {
     "end_time": "2025-02-06T11:57:40.360003Z",
     "start_time": "2025-02-06T11:57:40.355046Z"
    }
   },
   "source": [
    "class BahdanauAttention(nn.Module):\n",
    "    def __init__(self, hidden_dim=1024):\n",
    "        super().__init__()\n",
    "        self.Wk = nn.Linear(hidden_dim, hidden_dim) # 对keys做运算，encoder的输出EO\n",
    "        self.Wq = nn.Linear(hidden_dim, hidden_dim) # 对query做运算，decoder的隐藏状态\n",
    "        self.V = nn.Linear(hidden_dim, 1)\n",
    "\n",
    "    def forward(self, query, keys, values, attn_mask=None):\n",
    "        \"\"\"\n",
    "        正向传播\n",
    "        :param query: hidden state，是decoder的隐藏状态，shape = [batch size, hidden_dim]\n",
    "        :param keys: EO  [batch size, sequence length, hidden_dim]\n",
    "        :param values: EO  [batch size, sequence length, hidden_dim]\n",
    "        :param attn_mask:[batch size, sequence length]，这里是encoder_inputs_mask\n",
    "        :return:\n",
    "        \"\"\"\n",
    "        # query.shape = [batch size, hidden_dim] -->通过unsqueeze(-2)增加维度 [batch size, 1, hidden_dim]\n",
    "        # keys.shape = [batch size, sequence length, hidden_dim]\n",
    "        # values.shape = [batch size, sequence length, hidden_dim]\n",
    "        scores = self.V(F.tanh(self.Wk(keys) + self.Wq(query.unsqueeze(-2))))  # unsqueeze(-2)增加1个1的维度\n",
    "        # score.shape = [batch size, sequence length, 1]\n",
    "        if attn_mask is not None:  # 这个mask是encoder_inputs_mask，用来mask掉padding的部分,让padding部分socres为0\n",
    "            # attn_mask是1个矩阵，1表示要mask它的logits，0表示不mask\n",
    "            # 这里给mask为1的位置-inf，这样在softmax的时候，这个位置的logits就变成0了\n",
    "            # 在最后增加一个维度，[batch size, sequence length] --> [batch size, sequence length, 1]，并且1变为-inf，求和后经过softmax变0\n",
    "            attn_mask = (attn_mask.unsqueeze(-1)) * -1e16\n",
    "            scores += attn_mask\n",
    "        attention_weights = F.softmax(scores, dim=-2) # 对每一个batch词的score做softmax得到attention_weights\n",
    "        # attention_weights.shape = [batch size, sequence length, 1]\n",
    "        # 对每一个词的score和对应的value做乘法，然后在seq_len维度上求和，得到context_vector\n",
    "        context_vector = torch.mul(attention_weights, values).sum(dim=-2)\n",
    "        # context_vector.shape = [batch size, hidden_dim]\n",
    "        # attention_weights用于最后的画图\n",
    "        return context_vector, attention_weights"
   ],
   "outputs": [],
   "execution_count": 12
  },
  {
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-02-06T10:49:12.076685Z",
     "start_time": "2025-02-06T10:49:12.070710Z"
    },
    "id": "elw6vhzZTsvw",
    "outputId": "927a37e6-88d6-4c96-cf31-7f104f1d7730"
   },
   "cell_type": "code",
   "source": [
    "# 举例 attention_weights = F.softmax(scores, dim=-2)\n",
    "scores = torch.tensor([[[1.0], [2.0], [3.0]],\n",
    "                       [[0.5], [1.5], [2.5]]])\n",
    "print(scores.shape)\n",
    "print(scores.sum(dim=-2))\n",
    "scores = F.softmax(scores, dim=-2)\n",
    "e1 = torch.exp(torch.tensor(1.0))\n",
    "e2 = torch.exp(torch.tensor(2.0))\n",
    "e3 = torch.exp(torch.tensor(3.0))\n",
    "print(e1/(e1+e2+e3), e2/(e1+e2+e3), e3/(e1+e2+e3))\n",
    "print(scores)"
   ],
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "torch.Size([2, 3, 1])\n",
      "tensor([[6.0000],\n",
      "        [4.5000]])\n",
      "tensor(0.0900) tensor(0.2447) tensor(0.6652)\n",
      "tensor([[[0.0900],\n",
      "         [0.2447],\n",
      "         [0.6652]],\n",
      "\n",
      "        [[0.0900],\n",
      "         [0.2447],\n",
      "         [0.6652]]])\n"
     ]
    }
   ],
   "execution_count": null
  },
  {
   "cell_type": "code",
   "source": [
    "# tensor矩阵相乘mm\n",
    "a = torch.randn(2, 3)\n",
    "b = torch.randn(3, 2)\n",
    "c = torch.mm(a, b)  # 增加维度，mm是矩阵乘法\n",
    "print(c.shape)"
   ],
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-02-06T10:46:29.665256Z",
     "start_time": "2025-02-06T10:46:29.655980Z"
    },
    "id": "8CQCKwoTTsvx",
    "outputId": "3712d3f3-92c5-4046-f519-9a3b881d7839"
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "torch.Size([2, 2])\n"
     ]
    }
   ],
   "execution_count": null
  },
  {
   "cell_type": "code",
   "source": [
    "# 把上面的BahdanauAttention写一个例子，看看输出的shape\n",
    "attention = BahdanauAttention(hidden_dim=1024)\n",
    "query = torch.randn(2, 1024) # Decoder的隐藏状态\n",
    "keys = torch.randn(2, 50, 1024) # EO\n",
    "values = torch.randn(2, 50, 1024) # EO\n",
    "attn_mask = torch.randint(0, 2, (2, 50))\n",
    "context_vector, scores = attention(query, keys, values, attn_mask)\n",
    "print(context_vector.shape)\n",
    "print(scores.shape)"
   ],
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-02-05T12:12:45.011109Z",
     "start_time": "2025-02-05T12:12:44.969277Z"
    },
    "id": "P3secMZuTsvx",
    "outputId": "e3596bd7-1c23-45a7-9945-01c2ceeb8e02"
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "torch.Size([2, 1024])\n",
      "torch.Size([2, 50, 1])\n"
     ]
    }
   ],
   "execution_count": null
  },
  {
   "metadata": {
    "id": "EPb6EsTGTsvx"
   },
   "cell_type": "markdown",
   "source": [
    "### Decoder"
   ]
  },
  {
   "cell_type": "code",
   "metadata": {
    "id": "6W5FeRRrJzbd",
    "ExecuteTime": {
     "end_time": "2025-02-06T11:02:16.474232Z",
     "start_time": "2025-02-06T11:02:16.467743Z"
    }
   },
   "source": [
    "class Decoder(nn.Module):\n",
    "    def __init__(\n",
    "        self,\n",
    "        vocab_size,\n",
    "        embedding_dim=256,\n",
    "        hidden_dim=1024,\n",
    "        num_layers=1,\n",
    "        ):\n",
    "        super(Decoder, self).__init__()\n",
    "        self.embedding = nn.Embedding(vocab_size, embedding_dim)\n",
    "        self.gru = nn.GRU(embedding_dim + hidden_dim, hidden_dim, num_layers=num_layers, batch_first=True)  # 拼接\n",
    "        self.fc = nn.Linear(hidden_dim, vocab_size)  # 最后分类，词典大小个类\n",
    "        self.dropout = nn.Dropout(0.6)\n",
    "        self.attention = BahdanauAttention(hidden_dim) # 注意力得到的context_vector\n",
    "\n",
    "    def forward(self, decoder_input, hidden, encoder_outputs, attn_mask=None):\n",
    "        # attn_mask是encoder_inputs_mask\n",
    "        # decoder_input.shape = [batch size, 1]，下面循环，原来是decoder_input.shape = [batch size, sequence length]\n",
    "        assert len(decoder_input.shape) == 2 and decoder_input.shape[-1] == 1, f\"decoder_input.shape = {decoder_input.shape}is not valid\"\n",
    "        # hidden.shape = [batch size, hidden_dim]，decoder_hidden,而第一次使用的是encoder的hidden(第1个没有decoder_hidden)\n",
    "        assert len(hidden.shape) == 2, f\"hidden.shape = {hidden.shape}\"\n",
    "        # encoder_outputs.shape = [batch size, sequence length, hidden_dim]\n",
    "        assert len(encoder_outputs.shape) == 3, f\"encoder_outputs.shape = {encoder_outputs.shape}\"\n",
    "\n",
    "        # context_vector.shape = [batch size, hidden_dim]\n",
    "        context_vector, attention_score = self.attention(\n",
    "            query=hidden, keys=encoder_outputs, values=encoder_outputs, attn_mask=attn_mask)\n",
    "        # decoder_input.shape = [batch size, 1]\n",
    "        embeds = self.embedding(decoder_input)\n",
    "        # embeds.shape = [batch size, 1, embedding_dim]\n",
    "        embeds = torch.cat([context_vector.unsqueeze(-2), embeds], dim=-1)\n",
    "        # 新的embeds.shape = [batch size, 1, embedding_dim + hidden_dim]\n",
    "        seq_output, hidden = self.gru(embeds)\n",
    "        # seq_output.shape = [batch size, 1, hidden_dim]\n",
    "        logits = self.fc(self.dropout(seq_output))\n",
    "        # logits.shape = [batch size, 1, vocab size]，attention_score = [batch size, sequence length, 1]\n",
    "        return logits, hidden, attention_score"
   ],
   "outputs": [],
   "execution_count": 13
  },
  {
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-02-06T11:00:49.757271Z",
     "start_time": "2025-02-06T11:00:49.752290Z"
    },
    "id": "APS0P259Tsvx",
    "outputId": "4aebe851-e59c-45e6-8a99-6585764a5293"
   },
   "cell_type": "code",
   "source": [
    "# cat举例\n",
    "tensor1 = torch.tensor([[1,2,3], [4,5,6]])\n",
    "tensor2 = torch.tensor([[0,1,2], [3,4,5]])\n",
    "print(tensor1.shape)\n",
    "print(torch.cat([tensor1, tensor2], dim=-1))"
   ],
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "torch.Size([2, 3])\n",
      "tensor([[1, 2, 3, 0, 1, 2],\n",
      "        [4, 5, 6, 3, 4, 5]])\n"
     ]
    }
   ],
   "execution_count": null
  },
  {
   "metadata": {
    "id": "gZXCow3nTsvy"
   },
   "cell_type": "markdown",
   "source": [
    "### Seq2Seq模型"
   ]
  },
  {
   "cell_type": "code",
   "metadata": {
    "id": "FG-Pid9cJzbd",
    "ExecuteTime": {
     "end_time": "2025-02-06T12:00:23.070172Z",
     "start_time": "2025-02-06T12:00:23.062624Z"
    }
   },
   "source": [
    "class Sequence2Sequence(nn.Module):\n",
    "    def __init__(\n",
    "        self,\n",
    "        src_vocab_size, # 输入词典大小\n",
    "        trg_vocab_size, # 输出词典大小\n",
    "        encoder_embedding_dim=256,\n",
    "        encoder_hidden_dim=1024,  # encoder_hidden_dim = decoder_hidden_dim(BahdanauAttention设计的要求)\n",
    "        encoder_num_layers=1,\n",
    "        decoder_embedding_dim=256,\n",
    "        decoder_hidden_dim=1024,\n",
    "        decoder_num_layers=1,\n",
    "        bos_idx=1,\n",
    "        eos_idx=3,\n",
    "        max_length=512,\n",
    "        ):\n",
    "        super(Sequence2Sequence, self).__init__()\n",
    "        self.bos_idx = bos_idx\n",
    "        self.eos_idx = eos_idx\n",
    "        self.max_length = max_length\n",
    "        self.encoder = Encoder(\n",
    "            src_vocab_size,\n",
    "            embedding_dim=encoder_embedding_dim,\n",
    "            hidden_dim=encoder_hidden_dim,\n",
    "            num_layers=encoder_num_layers,\n",
    "            )\n",
    "        self.decoder = Decoder(\n",
    "            trg_vocab_size,\n",
    "            embedding_dim=decoder_embedding_dim,\n",
    "            hidden_dim=decoder_hidden_dim,\n",
    "            num_layers=decoder_num_layers,\n",
    "            )\n",
    "\n",
    "    def forward(self, *, encoder_inputs, decoder_inputs, attn_mask=None):\n",
    "        # encoding\n",
    "        encoder_outputs, hidden = self.encoder(encoder_inputs)\n",
    "        # decoding with teacher forcing\n",
    "        bs, seq_len = decoder_inputs.shape\n",
    "        logits_list = []\n",
    "        scores_list = []\n",
    "        for i in range(seq_len):  # 串行训练\n",
    "            # 每次迭代生成一个时间步的预测，存储在 logits_list 中，并且记录注意力分数（如果有的话）在 scores_list 中，最后将预测的logits和注意力分数拼接并返回。\n",
    "            logits, hidden, score = self.decoder(\n",
    "                decoder_inputs[:, i:i+1],  # 第i个时间步的decoder_inputs\n",
    "                hidden[-1], #取最后一层的hidden\n",
    "                encoder_outputs,\n",
    "                attn_mask=attn_mask\n",
    "                )\n",
    "            logits_list.append(logits)  # 记录预测的logits，用于计算损失  [ batch size, 1 , vocab size]\n",
    "            scores_list.append(score)  # 记录注意力分数,用于画图   [batch size, encoder_seqlen,1]\n",
    "\n",
    "        # 把logits拼成一个张量[ batch size, seq_len , vocab size];所有时间步的score拼成一个张量[batch size, encoder_seqlen,decoder_seqlen]\n",
    "        return torch.cat(logits_list, dim=-2), torch.cat(scores_list, dim=-1)\n",
    "\n",
    "\n",
    "    # 预测推理\n",
    "    @torch.no_grad()  # 不计算梯度，减少内存消耗\n",
    "    def infer(self, encoder_input, attn_mask=None):\n",
    "        # encoder_input.shape = [1, sequence length]每次取1个句子\n",
    "        # hidden.shape [ num_layers, batch size, hidden_dim]\n",
    "        encoder_outputs, hidden = self.encoder(encoder_input)\n",
    "\n",
    "        # decoding，[[1]]\n",
    "        decoder_input = torch.Tensor([self.bos_idx]).reshape(1, 1).to(dtype=torch.int64) # shape为[1,1]，内容为开始标记\n",
    "        decoder_pred = None\n",
    "        pred_list = [] #预测序列\n",
    "        score_list = []\n",
    "        # 从开始标记 bos_idx 开始，迭代地生成序列，直到生成结束标记 eos_idx 或达到最大长度 max_length。\n",
    "        for _ in range(self.max_length):\n",
    "            # decoder_input:[1,1] -> logits:[1,1,vocab_size]\n",
    "            logits, hidden, score = self.decoder(\n",
    "                decoder_input,\n",
    "                hidden[-1],\n",
    "                encoder_outputs,\n",
    "                attn_mask=attn_mask\n",
    "                )\n",
    "            # using greedy search（不用温度），logits.shape =[1,1,vocabsize]\n",
    "            decoder_pred = logits.argmax(dim=-1)  # 得到[1,1]\n",
    "            decoder_input = decoder_pred\n",
    "            pred_list.append(decoder_pred.reshape(-1).item()) # decoder_pred从(1,1)变为（1）标量\n",
    "            score_list.append(score) # 记录注意力分数,用于画图\n",
    "\n",
    "            # stop at eos token\n",
    "            if decoder_pred == self.eos_idx:\n",
    "                break\n",
    "\n",
    "        return pred_list, torch.cat(score_list, dim=-1)"
   ],
   "outputs": [],
   "execution_count": 14
  },
  {
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-02-06T11:41:41.286296Z",
     "start_time": "2025-02-06T11:41:41.281803Z"
    },
    "id": "nQUxYuXBTsvy",
    "outputId": "1cf2914a-af14-4855-9034-ea0974eddf40"
   },
   "cell_type": "code",
   "source": [
    "# hidden[-1],取第1个维度num_layers的最后一层的hidden，即最后一层的hidden\n",
    "t1 = torch.tensor([[[1.0], [2.0], [3.0]],\n",
    "                   [[4.0], [5.0], [6.0]]])\n",
    "print(t1.shape)\n",
    "print(t1[-1])\n",
    "t2 = torch.tensor([[[1.0, 1.1], [2.0, 2.1], [3.0, 3.1]],\n",
    "                       [[4.0, 4.1], [5.0, 5.1], [6.0, 6.1]]])\n",
    "print(t2.shape)\n",
    "print(t2[-1])"
   ],
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "torch.Size([2, 3, 1])\n",
      "tensor([[4.],\n",
      "        [5.],\n",
      "        [6.]])\n",
      "torch.Size([2, 3, 2])\n",
      "tensor([[4.0000, 4.1000],\n",
      "        [5.0000, 5.1000],\n",
      "        [6.0000, 6.1000]])\n"
     ]
    }
   ],
   "execution_count": null
  },
  {
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-02-06T12:00:26.603479Z",
     "start_time": "2025-02-06T12:00:26.167502Z"
    },
    "id": "l5OVCFP4Tsvz",
    "outputId": "fda3852a-576d-42c8-939f-a1260a358c02"
   },
   "cell_type": "code",
   "source": [
    "model = Sequence2Sequence(src_vocab_size=len(src_word2idx), trg_vocab_size=len(trg_word2idx))\n",
    "# 前向传播看输出的shape\n",
    "encoder_inputs = torch.randint(0,100,(2,50))\n",
    "decoder_inputs = torch.randint(0,100,(2,50))\n",
    "attn_mask = torch.randint(0, 2, (2,50))\n",
    "logits, scores = model(encoder_inputs=encoder_inputs, decoder_inputs=decoder_inputs, attn_mask=attn_mask)\n",
    "print(logits.shape)\n",
    "print(scores.shape)"
   ],
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "torch.Size([2, 50, 12521])\n",
      "torch.Size([2, 50, 50])\n"
     ]
    }
   ],
   "execution_count": null
  },
  {
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-02-06T11:52:04.749126Z",
     "start_time": "2025-02-06T11:52:04.599398Z"
    },
    "id": "FrMMQMiaTsv0",
    "outputId": "c4b1f9a6-3452-4da9-fbdb-7ef758dc486e"
   },
   "cell_type": "code",
   "source": [
    "def count_parameters(model):\n",
    "    return sum(p.numel() for p in model.parameters() if p.requires_grad)\n",
    "model = Sequence2Sequence(src_vocab_size=len(src_word2idx), trg_vocab_size=len(trg_word2idx),encoder_num_layers=1,decoder_num_layers=1)\n",
    "print(count_parameters(model))"
   ],
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "35247594\n"
     ]
    }
   ],
   "execution_count": null
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "id": "zE-vNp-xJzbe"
   },
   "source": [
    "## 训练"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "id": "o55JWSvhJzbe"
   },
   "source": [
    "### 损失函数"
   ]
  },
  {
   "cell_type": "code",
   "metadata": {
    "id": "c_Mmw5GAJzbe",
    "ExecuteTime": {
     "end_time": "2025-02-06T12:04:27.628152Z",
     "start_time": "2025-02-06T12:04:27.622993Z"
    }
   },
   "source": [
    "def cross_entropy_with_padding(logits, labels, padding_mask=None):\n",
    "    # logits.shape = [batch size, sequence length, num of classes] nc即vocab_size\n",
    "    # labels.shape = [batch size, sequence length]\n",
    "    # padding_mask.shape = [batch size, sequence length],decoder_labels_mask\n",
    "    bs, seq_len, nc = logits.shape\n",
    "    # 交叉熵损失只能算2维的\n",
    "    loss = F.cross_entropy(logits.reshape(bs * seq_len, nc), labels.reshape(-1), reduction='none') # reduction='none'表示不对batch求平均\n",
    "    if padding_mask is None:  # 如果没有padding_mask，就直接求平均\n",
    "        loss = loss.mean()\n",
    "    else:\n",
    "        # 如果提供了 padding_mask，则将填充部分的损失去除后计算有效损失的均值。首先，通过将 padding_mask reshape 成一维张量，并取 1 减去得到填充掩码。这样填充部分的掩码值变为 0，非填充部分变为 1。将损失张量与填充掩码相乘，这样填充部分的损失就会变为 0。然后，计算非填充部分的损失和（sum）以及非填充部分的掩码数量（sum）作为有效损失的均值计算。(因为上面我们设计的mask的token是0，所以这里是1-padding_mask)\n",
    "        padding_mask = 1 - padding_mask.reshape(-1) #将padding_mask reshape成一维张量，mask部分为0，非mask部分为1\n",
    "        loss = torch.mul(loss, padding_mask).sum() / padding_mask.sum()\n",
    "\n",
    "    return loss"
   ],
   "outputs": [],
   "execution_count": 15
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "id": "ITY9VUiiJzbe"
   },
   "source": [
    "### Callback"
   ]
  },
  {
   "cell_type": "code",
   "metadata": {
    "id": "qez1fjOPJzbe",
    "ExecuteTime": {
     "end_time": "2025-02-06T12:10:56.978959Z",
     "start_time": "2025-02-06T12:10:52.188905Z"
    }
   },
   "source": [
    "from torch.utils.tensorboard import SummaryWriter\n",
    "\n",
    "\n",
    "class TensorBoardCallback:\n",
    "    def __init__(self, log_dir, flush_secs=10):\n",
    "        \"\"\"\n",
    "        Args:\n",
    "            log_dir (str): dir to write log.\n",
    "            flush_secs (int, optional): write to dsk each flush_secs seconds. Defaults to 10.\n",
    "        \"\"\"\n",
    "        self.writer = SummaryWriter(log_dir=log_dir, flush_secs=flush_secs)\n",
    "\n",
    "    def draw_model(self, model, input_shape):\n",
    "        self.writer.add_graph(model, input_to_model=torch.randn(input_shape))\n",
    "\n",
    "    def add_loss_scalars(self, step, loss, val_loss):\n",
    "        self.writer.add_scalars(\n",
    "            main_tag=\"training/loss\",\n",
    "            tag_scalar_dict={\"loss\": loss, \"val_loss\": val_loss},\n",
    "            global_step=step,\n",
    "            )\n",
    "\n",
    "    def add_acc_scalars(self, step, acc, val_acc):\n",
    "        self.writer.add_scalars(\n",
    "            main_tag=\"training/accuracy\",\n",
    "            tag_scalar_dict={\"accuracy\": acc, \"val_accuracy\": val_acc},\n",
    "            global_step=step,\n",
    "        )\n",
    "\n",
    "    def add_lr_scalars(self, step, learning_rate):\n",
    "        self.writer.add_scalars(\n",
    "            main_tag=\"training/learning_rate\",\n",
    "            tag_scalar_dict={\"learning_rate\": learning_rate},\n",
    "            global_step=step,\n",
    "\n",
    "        )\n",
    "\n",
    "    def __call__(self, step, **kwargs):\n",
    "        # add loss\n",
    "        loss = kwargs.pop(\"loss\", None)\n",
    "        val_loss = kwargs.pop(\"val_loss\", None)\n",
    "        if loss is not None and val_loss is not None:\n",
    "            self.add_loss_scalars(step, loss, val_loss)\n",
    "        # add acc\n",
    "        acc = kwargs.pop(\"acc\", None)\n",
    "        val_acc = kwargs.pop(\"val_acc\", None)\n",
    "        if acc is not None and val_acc is not None:\n",
    "            self.add_acc_scalars(step, acc, val_acc)\n",
    "        # add lr\n",
    "        learning_rate = kwargs.pop(\"lr\", None)\n",
    "        if learning_rate is not None:\n",
    "            self.add_lr_scalars(step, learning_rate)"
   ],
   "outputs": [],
   "execution_count": 16
  },
  {
   "cell_type": "code",
   "metadata": {
    "id": "wXtxS8ukJzbe",
    "ExecuteTime": {
     "end_time": "2025-02-06T12:10:56.983500Z",
     "start_time": "2025-02-06T12:10:56.979948Z"
    }
   },
   "source": [
    "class SaveCheckpointsCallback:\n",
    "    def __init__(self, save_dir, save_step=5000, save_best_only=True):\n",
    "        \"\"\"\n",
    "        Save checkpoints each save_epoch epoch.\n",
    "        We save checkpoint by epoch in this implementation.\n",
    "        Usually, training scripts with pytorch evaluating model and save checkpoint by step.\n",
    "\n",
    "        Args:\n",
    "            save_dir (str): dir to save checkpoint\n",
    "            save_epoch (int, optional): the frequency to save checkpoint. Defaults to 1.\n",
    "            save_best_only (bool, optional): If True, only save the best model or save each model at every epoch.\n",
    "        \"\"\"\n",
    "        self.save_dir = save_dir\n",
    "        self.save_step = save_step\n",
    "        self.save_best_only = save_best_only\n",
    "        self.best_metrics = - np.inf\n",
    "\n",
    "        # mkdir\n",
    "        if not os.path.exists(self.save_dir):\n",
    "            os.mkdir(self.save_dir)\n",
    "\n",
    "    def __call__(self, step, state_dict, metric=None):\n",
    "        if step % self.save_step > 0:\n",
    "            return\n",
    "\n",
    "        if self.save_best_only:\n",
    "            assert metric is not None\n",
    "            if metric >= self.best_metrics:\n",
    "                # save checkpoints\n",
    "                torch.save(state_dict, os.path.join(self.save_dir, \"best.ckpt\"))\n",
    "                # update best metrics\n",
    "                self.best_metrics = metric\n",
    "        else:\n",
    "            torch.save(state_dict, os.path.join(self.save_dir, f\"{step}.ckpt\"))"
   ],
   "outputs": [],
   "execution_count": 17
  },
  {
   "cell_type": "code",
   "metadata": {
    "id": "lfzfWswRJzbe",
    "ExecuteTime": {
     "end_time": "2025-02-06T12:10:56.995145Z",
     "start_time": "2025-02-06T12:10:56.983500Z"
    }
   },
   "source": [
    "class EarlyStopCallback:\n",
    "    def __init__(self, patience=5, min_delta=0.01):\n",
    "        \"\"\"\n",
    "\n",
    "        Args:\n",
    "            patience (int, optional): Number of epochs with no improvement after which training will be stopped.. Defaults to 5.\n",
    "            min_delta (float, optional): Minimum change in the monitored quantity to qualify as an improvement, i.e. an absolute\n",
    "                change of less than min_delta, will count as no improvement. Defaults to 0.01.\n",
    "        \"\"\"\n",
    "        self.patience = patience\n",
    "        self.min_delta = min_delta\n",
    "        self.best_metric = - np.inf\n",
    "        self.counter = 0\n",
    "\n",
    "    def __call__(self, metric):\n",
    "        if metric >= self.best_metric + self.min_delta:\n",
    "            # update best metric\n",
    "            self.best_metric = metric\n",
    "            # reset counter\n",
    "            self.counter = 0\n",
    "        else:\n",
    "            self.counter += 1\n",
    "\n",
    "    @property\n",
    "    def early_stop(self):\n",
    "        return self.counter >= self.patience"
   ],
   "outputs": [],
   "execution_count": 18
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "id": "F2f3S6z7Jzbf"
   },
   "source": [
    "### training & evaluating"
   ]
  },
  {
   "cell_type": "code",
   "metadata": {
    "id": "IOlJp26YJzbf",
    "ExecuteTime": {
     "end_time": "2025-02-06T12:10:39.665708Z",
     "start_time": "2025-02-06T12:10:39.660995Z"
    }
   },
   "source": [
    "@torch.no_grad()\n",
    "def evaluating(model, dataloader, loss_fct):\n",
    "    loss_list = []\n",
    "    for batch in dataloader:\n",
    "        encoder_inputs = batch[\"encoder_inputs\"]\n",
    "        encoder_inputs_mask = batch[\"encoder_inputs_mask\"]\n",
    "        decoder_inputs = batch[\"decoder_inputs\"]\n",
    "        decoder_labels = batch[\"decoder_labels\"]\n",
    "        decoder_labels_mask = batch[\"decoder_labels_mask\"]\n",
    "\n",
    "        # 前向计算\n",
    "        logits, _ = model(\n",
    "            encoder_inputs=encoder_inputs,\n",
    "            decoder_inputs=decoder_inputs,\n",
    "            attn_mask=encoder_inputs_mask\n",
    "            ) #model就是seq2seq模型\n",
    "        loss = loss_fct(logits, decoder_labels, padding_mask=decoder_labels_mask)  # 验证集损失\n",
    "        loss_list.append(loss.cpu().item())\n",
    "\n",
    "    return np.mean(loss_list)"
   ],
   "outputs": [],
   "execution_count": 19
  },
  {
   "cell_type": "code",
   "metadata": {
    "id": "brzx2uFHJzbf",
    "ExecuteTime": {
     "end_time": "2025-02-06T12:10:59.241443Z",
     "start_time": "2025-02-06T12:10:59.048652Z"
    }
   },
   "source": [
    "# 训练\n",
    "def training(\n",
    "    model,\n",
    "    train_loader,\n",
    "    val_loader,\n",
    "    epoch,\n",
    "    loss_fct,\n",
    "    optimizer,\n",
    "    tensorboard_callback=None,\n",
    "    save_ckpt_callback=None,\n",
    "    early_stop_callback=None,\n",
    "    eval_step=500,\n",
    "    ):\n",
    "    record_dict = {\n",
    "        \"train\": [],\n",
    "        \"val\": []\n",
    "    }\n",
    "\n",
    "    global_step = 1\n",
    "    model.train() # 切换到训练模式\n",
    "    with tqdm(total=epoch * len(train_loader)) as pbar:\n",
    "        for epoch_id in range(epoch):\n",
    "            # training\n",
    "            for batch in train_loader:\n",
    "                encoder_inputs = batch[\"encoder_inputs\"]\n",
    "                encoder_inputs_mask = batch[\"encoder_inputs_mask\"]\n",
    "                decoder_inputs = batch[\"decoder_inputs\"]\n",
    "                decoder_labels = batch[\"decoder_labels\"]\n",
    "                decoder_labels_mask = batch[\"decoder_labels_mask\"]\n",
    "\n",
    "                # 梯度清空\n",
    "                optimizer.zero_grad()\n",
    "\n",
    "                # 前向计算\n",
    "                logits, _ = model(\n",
    "                    encoder_inputs=encoder_inputs,\n",
    "                    decoder_inputs=decoder_inputs,\n",
    "                    attn_mask=encoder_inputs_mask\n",
    "                    )\n",
    "                loss = loss_fct(logits, decoder_labels, padding_mask=decoder_labels_mask)\n",
    "                # 梯度回传\n",
    "                loss.backward()\n",
    "                # 调整优化器，包括学习率的变动等\n",
    "                optimizer.step()\n",
    "                loss = loss.cpu().item()\n",
    "                # record\n",
    "                record_dict[\"train\"].append({\n",
    "                    \"loss\": loss, \"step\": global_step\n",
    "                })\n",
    "                # evaluating\n",
    "                if global_step % eval_step == 0:\n",
    "                    model.eval() # 切换到验证模式\n",
    "                    val_loss = evaluating(model, val_loader, loss_fct)\n",
    "                    record_dict[\"val\"].append({\n",
    "                        \"loss\": val_loss, \"step\": global_step\n",
    "                    })\n",
    "                    model.train() # 切换到训练模式\n",
    "\n",
    "                    # 1. 使用 tensorboard 可视化\n",
    "                    if tensorboard_callback is not None:\n",
    "                        tensorboard_callback(\n",
    "                            global_step,\n",
    "                            loss=loss, val_loss=val_loss,\n",
    "                            lr=optimizer.param_groups[0][\"lr\"],\n",
    "                            )\n",
    "\n",
    "                    # 2. 保存模型权重 save model checkpoint\n",
    "                    if save_ckpt_callback is not None:\n",
    "                        save_ckpt_callback(global_step, model.state_dict(), metric=-val_loss)\n",
    "\n",
    "                    # 3. 早停 Early Stop\n",
    "                    if early_stop_callback is not None:\n",
    "                        early_stop_callback(-val_loss)\n",
    "                        if early_stop_callback.early_stop:\n",
    "                            print(f\"Early stop at epoch {epoch_id} / global_step {global_step}\")\n",
    "                            return record_dict\n",
    "\n",
    "                # update step\n",
    "                global_step += 1\n",
    "                pbar.update(1)\n",
    "            pbar.set_postfix({\"epoch\": epoch_id, \"loss\": loss, \"val_loss\": val_loss}) # 更新进度条\n",
    "\n",
    "    return record_dict\n",
    "\n",
    "\n",
    "epoch = 20\n",
    "batch_size = 64\n",
    "\n",
    "model = Sequence2Sequence(src_vocab_size=len(src_word2idx), trg_vocab_size=len(trg_word2idx))\n",
    "train_dl = DataLoader(train_ds, batch_size=batch_size, shuffle=True, collate_fn=collate_fct)\n",
    "test_dl = DataLoader(test_ds, batch_size=batch_size, shuffle=False, collate_fn=collate_fct)\n",
    "\n",
    "# 1. 定义损失函数 采用交叉熵损失\n",
    "loss_fct = cross_entropy_with_padding\n",
    "# 2. 定义优化器 采用 adam\n",
    "# Optimizers specified in the torch.optim package\n",
    "optimizer = torch.optim.Adam(model.parameters(), lr=0.001)\n",
    "\n",
    "# 1. tensorboard 可视化\n",
    "if not os.path.exists(\"runs\"):\n",
    "    os.mkdir(\"runs\")\n",
    "exp_name = \"translate-seq2seq\"\n",
    "tensorboard_callback = TensorBoardCallback(f\"runs/{exp_name}\")\n",
    "# tensorboard_callback.draw_model(model, [1, MAX_LENGTH])\n",
    "# 2. save best\n",
    "if not os.path.exists(\"checkpoints\"):\n",
    "    os.makedirs(\"checkpoints\")\n",
    "save_ckpt_callback = SaveCheckpointsCallback(\n",
    "    f\"checkpoints/{exp_name}\", save_step=200, save_best_only=True)\n",
    "# 3. early stop\n",
    "early_stop_callback = EarlyStopCallback(patience=5)\n",
    "\n",
    "model = model.to(device)"
   ],
   "outputs": [],
   "execution_count": 20
  },
  {
   "metadata": {
    "jupyter": {
     "is_executing": true
    },
    "ExecuteTime": {
     "start_time": "2025-02-06T12:11:01.605440Z"
    },
    "colab": {
     "referenced_widgets": [
      "e712e832ad1541898150fac1428604ca",
      "c423f66222834a59b24e6c30c580d33f",
      "b3d1f36c71f54549ad8fd99f50d289ed",
      "212f179b111b4624869fbc461970c96c",
      "9f65f3c20aab4fcaa85ba5995ac03ae0",
      "2cbece938db9461fb678ad895c153ff9",
      "5d659ce3b72a4eb6900747882b99e51b",
      "67992ee2008249d29616b3c54c5d1bec",
      "67d524c40438453cb5cdae6f7bcd5543",
      "146cc90654c8488ab4a96bc81677b06c",
      "7dc93ff1d91e456a984ddcc2f483f7b2"
     ],
     "base_uri": "https://localhost:8080/",
     "height": 66
    },
    "id": "DTddWDYqTswG",
    "outputId": "8b871747-6a90-4b2f-8d7c-887dea8db889"
   },
   "cell_type": "code",
   "source": [
    "record = training(\n",
    "    model,\n",
    "    train_dl,\n",
    "    test_dl,\n",
    "    epoch,\n",
    "    loss_fct,\n",
    "    optimizer,\n",
    "    tensorboard_callback=None,\n",
    "    save_ckpt_callback=save_ckpt_callback,\n",
    "    early_stop_callback=early_stop_callback,\n",
    "    eval_step=200\n",
    "    )"
   ],
   "outputs": [
    {
     "output_type": "display_data",
     "data": {
      "text/plain": [
       "  0%|          | 0/33500 [00:00<?, ?it/s]"
      ],
      "application/vnd.jupyter.widget-view+json": {
       "version_major": 2,
       "version_minor": 0,
       "model_id": "e712e832ad1541898150fac1428604ca"
      }
     },
     "metadata": {}
    },
    {
     "output_type": "stream",
     "name": "stdout",
     "text": [
      "Early stop at epoch 5 / global_step 9200\n"
     ]
    }
   ],
   "execution_count": 21
  },
  {
   "metadata": {
    "id": "8v5ftd-nTswH",
    "colab": {
     "base_uri": "https://localhost:8080/",
     "height": 430
    },
    "outputId": "4f43679e-8120-492a-d121-fef1a6968d0e"
   },
   "cell_type": "code",
   "outputs": [
    {
     "output_type": "display_data",
     "data": {
      "text/plain": [
       "<Figure size 640x480 with 1 Axes>"
      ],
      "image/png": "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SuX8laVWq2G1WgGwHmoUuXuPesOiRYuEXq8X8+fPFwcPHhQPP/ywCA4OduoNT5577LHHRFBQkFi3bp1ITk6233Jzc+37PProo6Jhw4ZizZo1YseOHaJXr16iV69e9ueLhzoOGzZM7NmzR6xYsULUq1fP7VDHqVOnikOHDomPP/6YQx3L4DgqRAjWQXXZtm2b0Gg0YtasWeLYsWNiwYIFwmAwiO+//96+z5w5c0RwcLD47bffxN69e8XYsWPdDnPs1KmT2Lp1q9i0aZNo3ry50zDHjIwMERkZKe6++26xf/9+sWjRImEwGDjMsci9994rGjRoYB9u+ssvv4jw8HDx3HPP2fdhPdQMiggWQggxb9480bBhQ6HT6UT37t3Fli1b5C5SrQXA7e3rr7+275OXlycef/xxERISIgwGg7jppptEcnKy03FOnz4tRo4cKXx9fUV4eLh45plnhMlkctpn7dq1omPHjkKn04kmTZo4nYOcXR0sWAfV548//hDx8fFCr9eLVq1aiS+++MLpeavVKqZPny4iIyOFXq8XgwcPFkeOHHHa58qVK+LOO+8U/v7+IjAwUNx3330iKyvLaZ/ExETRt29fodfrRYMGDcScOXOq/L3VFkajUTz55JOiYcOGwsfHRzRp0kT897//dRoWynqoGbhsOhEREXlNre9jQURERDUHgwURERF5DYMFEREReQ2DBREREXkNgwURERF5DYMFEREReQ2DBREREXkNgwURERF5DYMFEREReQ2DBREREXkNgwURERF5DYMFERERec3/A6Im83WH0qAqAAAAAElFTkSuQmCC\n"
     },
     "metadata": {}
    }
   ],
   "execution_count": 22,
   "source": [
    "plt.plot([i[\"step\"] for i in record[\"train\"]], [i[\"loss\"] for i in record[\"train\"]], label=\"train\")\n",
    "plt.plot([i[\"step\"] for i in record[\"val\"]], [i[\"loss\"] for i in record[\"val\"]], label=\"val\")\n",
    "plt.grid()\n",
    "plt.legend()\n",
    "plt.show()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "id": "UOSROA66Jzbg"
   },
   "source": [
    "## 推理\n",
    "\n",
    "- 接下来进行翻译推理，并作出注意力的热度图"
   ]
  },
  {
   "cell_type": "code",
   "metadata": {
    "colab": {
     "base_uri": "https://localhost:8080/"
    },
    "id": "cX75BqcBJzbg",
    "outputId": "29a7059c-c2d6-4fa6-a883-4cc90bb5ad0d",
    "ExecuteTime": {
     "end_time": "2025-02-05T14:25:56.884132Z",
     "start_time": "2025-02-05T14:25:55.385681Z"
    }
   },
   "source": [
    "# load checkpoints,如何上线\n",
    "model = Sequence2Sequence(len(src_word2idx), len(trg_word2idx))\n",
    "model.load_state_dict(torch.load(f\"./checkpoints/translate-seq2seq/best.ckpt\", map_location=\"cpu\"))\n",
    "\n",
    "class Translator:\n",
    "    def __init__(self, model, src_tokenizer, trg_tokenizer):\n",
    "        self.model = model\n",
    "        self.model.eval() # 切换到验证模式\n",
    "        self.src_tokenizer = src_tokenizer\n",
    "        self.trg_tokenizer = trg_tokenizer\n",
    "\n",
    "    def draw_attention_map(self, scores, src_words_list, trg_words_list):\n",
    "        \"\"\"绘制注意力热力图\n",
    "        Args:\n",
    "            - scores (numpy.ndarray): shape = [source sequence length, target sequence length]\n",
    "        \"\"\"\n",
    "        plt.matshow(scores.T, cmap='viridis') # 注意力矩阵,显示注意力分数值\n",
    "\n",
    "        # 获取当前的轴\n",
    "        ax = plt.gca()\n",
    "        # 设置热图中每个单元格的分数的文本\n",
    "        for i in range(scores.shape[0]):  # 输入\n",
    "            for j in range(scores.shape[1]):  # 输出\n",
    "                ax.text(j, i, f'{scores[i, j]:.2f}',  # 格式化数字显示\n",
    "                               ha='center', va='center', color='k')\n",
    "\n",
    "        plt.xticks(range(scores.shape[0]), src_words_list)\n",
    "        plt.yticks(range(scores.shape[1]), trg_words_list)\n",
    "        plt.show()\n",
    "\n",
    "    def __call__(self, sentence):\n",
    "        sentence = preprocess_sentence(sentence) # 预处理句子，标点符号处理等\n",
    "        encoder_input, attn_mask = self.src_tokenizer.encode(\n",
    "            [sentence.split()],  # 二维列表\n",
    "            padding_first=True,\n",
    "            add_bos=True,\n",
    "            add_eos=True,\n",
    "            return_mask=True,\n",
    "            ) # 对输入进行编码，并返回encode_piadding_mask\n",
    "        encoder_input = torch.Tensor(encoder_input).to(dtype=torch.int64) # 转换成tensor\n",
    "\n",
    "        preds, scores = model.infer(encoder_input=encoder_input, attn_mask=attn_mask) # 预测\n",
    "\n",
    "        trg_sentence = self.trg_tokenizer.decode([preds], split=True, remove_eos=False)[0] # 通过tokenizer转换成文字\n",
    "\n",
    "        # 画注意力图\n",
    "        src_decoded = self.src_tokenizer.decode(\n",
    "            encoder_input.tolist(),\n",
    "            split=True,\n",
    "            remove_bos=False,\n",
    "            remove_eos=False\n",
    "            )[0] #对输入编码id进行解码，转换成文字,为了画图\n",
    "\n",
    "        self.draw_attention_map(\n",
    "            scores.squeeze(0).numpy(),\n",
    "            src_decoded, # 注意力图的源句子\n",
    "            trg_sentence # 注意力图的目标句子\n",
    "            )\n",
    "\n",
    "        return \" \".join(trg_sentence[:-1]) # list转字符串"
   ],
   "outputs": [
    {
     "output_type": "stream",
     "name": "stderr",
     "text": [
      "<ipython-input-24-bdd046588616>:3: FutureWarning: You are using `torch.load` with `weights_only=False` (the current default value), which uses the default pickle module implicitly. It is possible to construct malicious pickle data which will execute arbitrary code during unpickling (See https://github.com/pytorch/pytorch/blob/main/SECURITY.md#untrusted-models for more details). In a future release, the default value for `weights_only` will be flipped to `True`. This limits the functions that could be executed during unpickling. Arbitrary objects will no longer be allowed to be loaded via this mode unless they are explicitly allowlisted by the user via `torch.serialization.add_safe_globals`. We recommend you start setting `weights_only=True` for any use case where you don't have full control of the loaded file. Please open an issue on GitHub for any issues related to this experimental feature.\n",
      "  model.load_state_dict(torch.load(f\"./checkpoints/translate-seq2seq/best.ckpt\", map_location=\"cpu\"))\n"
     ]
    }
   ],
   "execution_count": 24
  },
  {
   "cell_type": "code",
   "execution_count": 25,
   "outputs": [
    {
     "output_type": "display_data",
     "data": {
      "text/plain": [
       "<Figure size 420x480 with 1 Axes>"
      ],
      "image/png": "iVBORw0KGgoAAAANSUhEUgAAAbEAAAGkCAYAAAC/yxuZAAAAOnRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjEwLjAsIGh0dHBzOi8vbWF0cGxvdGxpYi5vcmcvlHJYcgAAAAlwSFlzAAAPYQAAD2EBqD+naQAApdFJREFUeJzs3Xd8FHX+x/HXbM+2tE3vjRJK6F1BQcCOp5x6Vs5yp2dFPU9PRc9TbFgOPTm9s529IjYsSBFBeicEAoT0XneTrTO/PwIb1mwQcDHu777Px2MfmtnPTN6Z7M5n5jvfJZKiKAqCIAiCEIZUvR1AEARBEI6XaGKCIAhC2BJNTBAEQQhbookJgiAIYUs0MUEQBCFsiSYmCIIghC3RxARBEISwJZqYIAiCELZEExMEQRDC1v98E5s0aRKSJCFJEps3b+6VDCUlJf4MQ4YMOeb1J02axC233BLyXOEsMzOTp59++hf/voqicO211xITE3PE15QkSSxcuPAXzdYb7r///uN6Tf9/8Gs4tvwaMpxo//NNDOCaa66hqqqKgQMHBjQUSZLQ6XTk5uby97//nR//C107duzgt7/9LXFxcej1evr06cN9991He3t7QN2WLVs455xziI+Px2AwkJmZyYUXXkhtbS0AaWlpVFVVcdttt/1iP7NwYixevJhXXnmFTz/91P+aCqaqqorTTz/9F073y7v99ttZsmRJb8foNUc6thz++OGHH/zrdHR0MGfOHPr06YNer8dmszFz5kx27NgRsO329nbuuusucnJyMBgMxMXFMXHiRD7++GN/zYcffsjatWt/sZ+3N2h6O8CvgdFoJDExMWDZN998w4ABA3C5XKxcuZKrr76apKQkrrrqKgB++OEHpkyZwpQpU/jss89ISEhg7dq13HbbbSxZsoSlS5ei0+moq6tj8uTJnHXWWXz55ZdERUVRUlLCokWLcDgcAKjVahITEzGbzb/4zy6E1t69e0lKSmLcuHFBn3e73eh0um6vt/+vzGbz//Tr+kjHlsPFxsYC4HK5mDJlCqWlpcybN4/Ro0dTU1PD3LlzGT16NN988w1jxowB4I9//CNr1qxh/vz55Ofn09DQwKpVq2hoaPBvNyYmhtbW1hP8U/Yy5X/cxIkTlZtvvtn/9f79+xVA2bRpU0Dd5MmTleuvv15RFEWRZVnJz89XRowYofh8voC6zZs3K5IkKY888oiiKIry0UcfKRqNRvF4PD+ZZc6cOUpBQcFx/Qw33nijcscddyjR0dFKQkKCMmfOHP/z8+bNUwYOHKgYjUYlNTVVue6665S2traAbaxcuVKZOHGiEhERoURFRSlTp05VGhsbFUVRFJ/Ppzz88MNKZmamYjAYlMGDByvvvffeMeW74YYblJtvvlmJiopS4uPjlRdeeEGx2+3KlVdeqZjNZiUnJ0f5/PPPFUVRlJdfflmJjIwM2MZHH32k/PjlumjRImXEiBGKXq9XYmNjlRkzZvify8jIUB566CFl1qxZitlsVtLS0pR//etfAetv3bpVOeWUUxSDwaDExMQo11xzTbf9ciyuuOIKBfA/MjIylIkTJyp/+tOflJtvvlmJjY1VJk2apCiKogDKRx99dMKyHI0vvvhCGT9+vBIZGanExMQoZ555plJcXOx/fs2aNcqQIUMUvV6vDB8+XPnwww8D3htH83s63tf0/wdHe2w53COPPKJIkqRs3rw5YLnP51NGjBih5OfnK7IsK4qiKJGRkcorr7zykzmO5vuGMzGceBTWr1/Phg0bGD16NACbN29m586dzJ49G5UqcBcWFBQwZcoU3nrrLQASExPxer189NFH3YYjQ+nVV1/FZDKxZs0aHnvsMf72t7/x9ddfA6BSqfjHP/7Bjh07ePXVV/n222/585//7F938+bNTJ48mfz8fFavXs3KlSs5++yz8fl8AMydO5fXXnuNBQsWsGPHDm699VYuvfRSli9ffkz5bDYba9eu5cYbb+S6665j5syZjBs3jo0bNzJ16lQuu+yybkOxPfnss88477zzOOOMM9i0aRNLlixh1KhRATXz5s1jxIgRbNq0ieuvv57rrruOoqIiABwOB9OmTSM6Opp169bx3nvv8c0333DDDTcc9c/0Y8888wx/+9vfSE1NpaqqinXr1vl/dp1Ox/fff8+CBQu6rXcishwNh8PB7NmzWb9+PUuWLEGlUnHeeechyzJ2u52zzjqL/Px8NmzYwP3338/tt99+QvMI8Oabb3LaaadRUFAQsFylUnHrrbeyc+dOtmzZAnQeWz7//HPa2tp6I+qvR2930d7W09lSRESEYjKZFK1WqwDKtdde6695++23j3hmc9NNNykRERH+r++++25Fo9EoMTExyvTp05XHHntMqa6u7rbez7kSmzBhQsCykSNHKnfeeWfQ+vfee0+JjY31f33xxRcr48ePD1rrdDoVo9GorFq1KmD5VVddpVx88cXHlc/r9Somk0m57LLL/MuqqqoUQFm9evVRneGPHTtWueSSS3r8nhkZGcqll17q/1qWZSU+Pl55/vnnFUVRlBdeeEGJjo5W7Ha7v+azzz5TVCpV0N/N0XrqqaeUjIwM/9cTJ05Uhg4d2q2Ow67ETlSWY1VXV6cAyrZt25R//etfSmxsrNLR0eF//vnnnxdXYsfgp44thz8OMRgMAescbuPGjQqgvPPOO4qiKMry5cuV1NRURavVKiNGjFBuueUWZeXKld3WE1di/6PeeecdNm/ezJYtW3j33Xf5+OOP+ctf/hJQoxzlldVDDz1EdXU1CxYsYMCAASxYsIB+/fqxbdu2kOUdPHhwwNdJSUn+iSPffPMNkydPJiUlBYvFwmWXXUZDQ4P/qufQlVgwxcXFtLe3c9ppp/nvb5jNZl577TX27t17XPnUajWxsbEMGjTIvywhIQHAn/mnHClzsO8pSRKJiYn+7RcWFlJQUIDJZPLXjB8/HlmW/VdroTJ8+PAjPv9LZjncnj17uPjii8nOzsZqtZKZmQlAaWkphYWFDB48GIPB4K8fO3bsCcvyv+TQseXwx+GO9rhy8skns2/fPpYsWcIFF1zAjh07OOmkk3jwwQdPQOpfL9HEepCWlkZubi79+/dn5syZ3HLLLcybNw+n00mfPn2AzoNPMIWFhf6aQ2JjY5k5cyZPPPEEhYWFJCcn88QTT4Qsr1arDfhakiRkWaakpISzzjqLwYMH88EHH7Bhwwaee+45oHOSAUBERESP27Xb7UDn8N3hb7qdO3fy/vvv/6x8hy+TJAkAWZZRqVTd3sgejyfg6yNlPtL3lGX5qDOHyuHN6dfk7LPPprGxkRdffJE1a9awZs0aoOt18VOO5vckdHfo2HL445A+ffoc8bhyqOYQrVbLSSedxJ133slXX33F3/72Nx588MGj/h3+fyCa2FFSq9V4vV7cbjdDhgyhX79+PPXUU90Oilu2bOGbb77h4osv7nFbOp2OnJwc/+zEE2nDhg3Issy8efMYM2YMffr0obKyMqBm8ODBPU6Dzs/PR6/XU1pa2u2Nl5aWdkIyx8XF0dbWFrB/fny2eqTMR6N///5s2bIl4Ht8//33qFQq+vbte9zbDZcsDQ0NFBUVcc899zB58mT69+9PU1NTQKatW7fidDr9yw6fBg5H93sSjs1FF13EN99847/vdYgsyzz11FPk5+d3u192uPz8fLxeb8Dv7f870cR60NDQQHV1NeXl5XzxxRc888wznHLKKVitViRJ4j//+Q87d+7k/PPPZ+3atZSWlvLee+9x9tlnM3bsWP+Hjz/99FMuvfRSPv30U3bv3k1RURFPPPEEn3/+Oeeee+4J/zlyc3PxeDzMnz+fffv28d///rfb5IK77rqLdevWcf3117N161Z27drF888/T319PRaLhdtvv51bb72VV199lb1797Jx40bmz5/Pq6++ekIyjx49GqPRyN13383evXt58803eeWVVwJq5syZw1tvvcWcOXMoLCxk27ZtPProo0f9PS655BIMBgNXXHEF27dvZ+nSpdx4441cdtll/qHNX0pvZImOjiY2NpYXXniB4uJivv32W2bPnu1//ne/+x2SJHHNNdewc+dOPv/8824jB0fze/q1ePbZZ39y+PmXcujYcvjjUNO59dZbGTVqFGeffTbvvfcepaWlrFu3jvPPP5/CwkL+85//+EctJk2axL/+9S82bNhASUkJn3/+OXfffbf/OPW/QjSxHkyZMoWkpCQyMzO59tprOeOMM3jnnXf8z48bN44ffvgBtVrN6aefTm5uLnfddRdXXHEFX3/9NXq9Hug8MzIajdx2220MGTKEMWPG8O677/Lvf/+byy677IT/HAUFBTz55JM8+uijDBw4kDfeeIO5c+cG1PTp04evvvqKLVu2MGrUKMaOHcvHH3+MRtP5McIHH3yQe++9l7lz59K/f3+mT5/OZ599RlZW1gnJHBMTw+uvv87nn3/OoEGDeOutt7j//vsDaiZNmsR7773HokWLGDJkCKeeeuoxfajTaDTy5Zdf0tjYyMiRI7nggguYPHkyzz77bIh/ml9nFpVKxdtvv82GDRsYOHAgt956K48//rj/ebPZzCeffMK2bdsYOnQof/3rX7udJBzN7+nXor6+/pju4Z5Ih44thz8O/estBoOBb7/9lssvv5y7776b3Nxcpk+fjlqt5ocffvB/Rgxg2rRpvPrqq0ydOpX+/ftz4403Mm3aNN59991e+sl6h6Qc7V3E/6cmTZrEkCFDeuWfKPqx+++/n4ULF4ohGeFXqaSkhKysLDZt2vQ/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\n"
     },
     "metadata": {}
    },
    {
     "output_type": "execute_result",
     "data": {
      "text/plain": [
       "'it s been very cold here .'"
      ],
      "application/vnd.google.colaboratory.intrinsic+json": {
       "type": "string"
      }
     },
     "metadata": {},
     "execution_count": 25
    }
   ],
   "source": [
    "translator = Translator(model.cpu(), src_tokenizer, trg_tokenizer)\n",
    "translator(u'hace mucho frio aqui .')"
   ],
   "metadata": {
    "ExecuteTime": {
     "end_time": "2024-08-01T06:54:20.377291800Z",
     "start_time": "2024-08-01T06:54:19.612529200Z"
    },
    "id": "cSKC2S1FTswI",
    "outputId": "0522bb5d-d794-46e6-97cb-321bf87e2d57",
    "colab": {
     "base_uri": "https://localhost:8080/",
     "height": 455
    }
   }
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "outputs": [
    {
     "data": {
      "text/plain": "<Figure size 560x480 with 1 Axes>",
      "image/png": "iVBORw0KGgoAAAANSUhEUgAAAfcAAAHFCAYAAADvx7CBAAAAOXRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjguNCwgaHR0cHM6Ly9tYXRwbG90bGliLm9yZy8fJSN1AAAACXBIWXMAAA9hAAAPYQGoP6dpAABzDElEQVR4nO3deXhb5Znw/+/RbluL992xk9jZ94SEBEJCSdi3lqVQSoG29G07b1ug7dtm2il02oEOQwf4daYLnULLlK0tUFq2QgIBspCQxdk3O7HjxEu8S7IsyZKe3x9O5CiWkzixJefk/lzXuYiP7nP03NI559bznEdCU0ophBBCCKEbhmQ3QAghhBBDS4q7EEIIoTNS3IUQQgidkeIuhBBC6IwUdyGEEEJnpLgLIYQQOiPFXQghhNAZKe5CCCGEzkhxF0IIIXRGivtJLF68GE3T0DSNysrKpLShpqYm2oYZM2YkpQ1CnI2VK1eiaRodHR3JbspZe+ihh055Ht59993ceOONCWmPGJyRcE1PVBukuJ/CvffeS0NDA1OmTIkptJqmYbFYKC8v56c//Skn/orvjh07uPXWW8nJycFqtTJu3Dh+9KMf4fP5YuK2bNnC9ddfT25uLjabjbKyMj772c9y5MgRAEpKSmhoaODb3/52wnIeasdet2SdTCK5FixYQENDAy6XK9lNOWvf+c53WLFiRbKbIc7Cya7pxy8ff/xxdJvu7m4efPBBxo0bh9VqJTs7m1tuuYUdO3bE7Nvn87Fs2TLGjh2LzWYjJyeHRYsW8dprr0VjXnnlFdavXz/seZqG/RnOcampqeTn58esW758OZMnTyYQCLBq1Sq+/OUvU1BQwJe+9CUAPv74Y5YsWcKSJUt44403yMvLY/369Xz7299mxYoVvP/++1gsFpqbm7nsssu49tpr+cc//kF6ejo1NTX87W9/o6urCwCj0Uh+fj52uz3huQsxFCwWS79z6Fxlt9vlXDzHneyafrysrCwAAoEAS5Ys4eDBg/z85z9n3rx5NDU18cgjjzBv3jyWL1/OhRdeCMBXv/pV1q1bxy9+8QsmTZpEa2sra9asobW1NbrfzMxM3G73MGcJKDGgRYsWqW9961vRvw8cOKAAtXnz5pi4yy67TH39619XSikViUTUpEmT1Jw5c1Q4HI6Jq6ysVJqmqZ/97GdKKaVeffVVZTKZVE9Pzynb8uCDD6rp06efVT5nKxwOq4cffliVlZUpm82mpk2bpv785z8rpZRqa2tTn/vc51R2dray2WyqvLxcPf3000oppYCYZdGiRUoppdavX6+WLFmisrKylNPpVJdcconauHFjstI7I2f6mpzLFi1apP7v//2/6lvf+pZKT09Xubm56qmnnlJer1fdfffdym63q7Fjx6o333xTKaXU+++/rwDV3t6e3Iafht/85jeqoKCg37l7/fXXq3vuuaffeRgKhdT999+vXC6XyszMVN/97nfVF77wBXXDDTdEY9566y110UUXRWOuueYaVVVVlaCMxPFO95p+vJ/97GdK0zRVWVkZsz4cDqs5c+aoSZMmqUgkopRSyuVyqd///venbMfpPO/ZkmH5s7RhwwY2btzIvHnzAKisrGTnzp088MADGAyxL+/06dNZsmQJL7zwAgD5+fmEQiFeffXVfsP6I9EjjzzCs88+y69//Wt27NjB/fffz+c//3k++OAD/uVf/oWdO3fy1ltvsWvXLn71q1+RnZ0NEB2CWr58OQ0NDbzyyisAeDwe7rrrLlatWsXHH39MRUUFV199NR6PJ2k5DtaZvibnuj/84Q9kZ2ezfv16vvGNb/C1r32NW265hQULFrBp0yYuv/xy7rzzzn63oUa6W265hdbWVt5///3oura2Nt5++23uuOOOfvE///nP+f3vf8/TTz/NqlWraGtr49VXX42J6erq4oEHHmDDhg2sWLECg8HApz/9aSKRyLDnI87e888/z9KlS5k+fXrMeoPBwP3338/OnTvZsmUL0HtNf/PNN0fGNWzYPjbowECf8lJSUlRaWpoym80KUF/5yleiMS+++OJJP5F985vfVCkpKdG///mf/1mZTCaVmZmprrzySvXoo4+qxsbGftslu+fu9/tVamqqWrNmTcz6L33pS+r2229X1113nbrnnnvibnu6n1LD4bByOBzq73//+1A1e1idzWtyLlu0aJG6+OKLo3+HQiGVlpam7rzzzui6hoYGBai1a9eeUz13pZS64YYb1Be/+MXo37/5zW9UYWGhCofD/c7DgoIC9eijj0b/7unpUcXFxTE99xM1NzcrQG3btm04mi9O4lTX9OOXY2w2W8w2x9u0aZMC1EsvvaSUUuqDDz5QxcXFymw2qzlz5qj77rtPrVq1qt920nMfoV566SUq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+3b1pS99STmdTnXkyJFEpXVKw5H3XXfdpa688krV0NAQXdra2hKV0mkZjryPz7ehoUE9/fTTStM0VV1dnai0TmmweX/uc59T//3f/602b96sdu3ape6++27lcrnUoUOHlFJKdXR0qCVLlqiXXnpJ7d69W61du1bNnTtXzZ49O5FpndJg825tbY15L7dv366MRqN65plnojHPPvus+vGPf6x++9vfKkBt3rw5MckIJcX9DMydO1f90z/9U/TvcDisCgsL1SOPPHLKbUtLS+MW9+ON1OI+2LxvvfVWdc0118Ssmzdvnvo//+f/DPgcnZ2dClDLly8fmkYPgeHI+6677lI33HDDsLR3qCTi/b7hhhvUpz71qaFp8BA5m/NbKaVCoZByOBzqD3/4w4Ax69evV4Cqra096/YOlbPN+/HHH1cOh0N5vd5+jx04cECKe4LJsPwgBYNBNm7cyJIlS6LrDAYDS5YsYe3atUls2fA6k7zXrl0bEw9wxRVXDBgfDAZ56qmncLlcTJ8+fegafxaGM++VK1eSm5vL+PHj+drXvkZra+vQJ3CGEvF+NzU18cYbb/ClL31p6Bp+lobi/Pb5fPT09JCZmTlgTGdnJ5qmkZ6efrZNHhJDkffvfvc7brvtNtLS0oarmWIQpLgPUktLC+FwmLy8vJj1eXl5NDY2JqlVw+9M8m5sbDyt+Ndffx273Y7NZuPxxx/n3XffJTs7e2gTOEPDlfeVV17Js88+y4oVK/j3f/93PvjgA6666irC4fDQJ3EGhvP9PuYPf/gDDoeDz3zmM0PT6CEwFOf39773PQoLC/t90DnG7/fzve99j9tvvx2n03nWbR4KZ5v3+vXr2b59O1/+8peHq4likEzJboAQl156KZWVlbS0tPDb3/6WW2+9lXXr1pGbm5vspg2b2267LfrvqVOnMm3aNMaOHcvKlSu57LLLktiyxHn66ae54447sNlsyW7KkPnZz37Giy++yMqVK+Pm1dPTw6233opSil/96ldJaOHw+N3vfsfUqVOZO3duspsijpKe+yBlZ2djNBppamqKWd/U1ER+fn6SWjX8ziTv/Pz804pPS0ujvLycCy+8kN/97neYTCZ+97vfDW0CZ2g48z7emDFjyM7Opqqq6uwbPQSGO++PPvqIPXv2jLie3tmc34899hg/+9nPeOedd5g2bVq/x48V9traWt59990R02uHs8u7q6uLF198cUTdXhFS3AfNYrEwe/ZsVqxYEV0XiURYsWIF8+fPT2LLhteZ5D1//vyYeIB33333lK9TJBIhEAicfaOHQKLyPnToEK2trRQUFAxNw8/ScOf9u9/9jtmzZ4+YuRXHnOn5/eijj/KTn/yEt99+mzlz5vR7/Fhh37dvH8uXLycrK2tY2n+mzua69uc//5lAIMDnP//54W6mGIxkz+g7F7344ovKarWq3//+92rnzp3qK1/5ikpPT1eNjY1KKaXuvPNO9f3vfz8aHwgE1ObNm9XmzZtVQUGB+s53vqM2b96s9u3bF43xeDzRGED953/+p9q8efOImk072LxXr16tTCaTeuyxx9SuXbvUgw8+GPPVKK/Xq5YtW6bWrl2rampq1IYNG9Q999yjrFar2r59e1JyjGeo8/Z4POo73/mOWrt2rTpw4IBavny5mjVrlqqoqFB+vz8pOcYz1Hkf09nZqVJTU9WvfvWrhOZzugab989+9jNlsVjUX/7yl5ivhnk8HqWUUsFgUF1//fWquLhYVVZWxsQEAoGk5BjPYPM+5uKLL1af/exn4+6ztbVVbd68Wb3xxhsKUC+++KLavHmzamhoGNZchHwV7oz94he/UKNGjVIWi0XNnTtXffzxx9HHFi1apO66667o38e+BnLismjRomjM+++/Hzfm+P2MBIPJWyml/vSnP6lx48Ypi8WiJk+erN54443oY93d3erTn/60KiwsVBaLRRUUFKjrr79erV+/PlHpnLahzNvn86nLL79c5eTkKLPZrEpLS9W9994bvYiOJEOZ9zG/+c1vVEpKiuro6Bju5p+xweRdWloa99x98MEHlVIDn/+Aev/99xOb2CkM9v3evXu3AtQ777wTd3/PPPPMSV8bMXw0pZRKyBCBEEIIIRJC7rkLIYQQOiPFXQghhNAZKe5CCCGEzkhxF0IIIXRGirsQQgihM1LchRBCCJ2R4i6EEELojBR3IYQQQmekuAshhBA6I8VdCCGE0Bkp7kIIIYTO/P85oX0EJRhUegAAAABJRU5ErkJggg=="
     },
     "metadata": {},
     "output_type": "display_data"
    },
    {
     "data": {
      "text/plain": "'this is my life .'"
     },
     "execution_count": 29,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "translator(u'esta es mi vida.')"
   ],
   "metadata": {
    "ExecuteTime": {
     "end_time": "2024-05-06T03:11:41.298875400Z",
     "start_time": "2024-05-06T03:11:40.874116700Z"
    },
    "id": "B2RDvenpTswJ",
    "outputId": "0ee0579a-17de-4cc3-beaa-3240433c668e"
   }
  },
  {
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-02-05T14:06:29.775201Z",
     "start_time": "2025-02-05T14:06:21.802483Z"
    },
    "id": "BCAQcKw4TswJ",
    "outputId": "364af735-1ead-4e98-cba0-afe098d2c347"
   },
   "cell_type": "code",
   "source": [
    "# pip install nltk"
   ],
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Looking in indexes: https://mirrors.aliyun.com/pypi/simple/\n",
      "Collecting nltk\n",
      "  Downloading https://mirrors.aliyun.com/pypi/packages/4d/66/7d9e26593edda06e8cb531874633f7c2372279c3b0f46235539fe546df8b/nltk-3.9.1-py3-none-any.whl (1.5 MB)\n",
      "     ---------------------------------------- 0.0/1.5 MB ? eta -:--:--\n",
      "     -------- ------------------------------- 0.3/1.5 MB 6.7 MB/s eta 0:00:01\n",
      "     ---------------------- ----------------- 0.8/1.5 MB 8.9 MB/s eta 0:00:01\n",
      "     ------------------------------------ --- 1.4/1.5 MB 9.7 MB/s eta 0:00:01\n",
      "     ---------------------------------------- 1.5/1.5 MB 9.6 MB/s eta 0:00:00\n",
      "Collecting click (from nltk)\n",
      "  Downloading https://mirrors.aliyun.com/pypi/packages/7e/d4/7ebdbd03970677812aac39c869717059dbb71a4cfc033ca6e5221787892c/click-8.1.8-py3-none-any.whl (98 kB)\n",
      "     ---------------------------------------- 0.0/98.2 kB ? eta -:--:--\n",
      "     ---------------------------------------- 98.2/98.2 kB 5.5 MB/s eta 0:00:00\n",
      "Requirement already satisfied: joblib in c:\\users\\29470\\appdata\\local\\programs\\python\\python312\\lib\\site-packages (from nltk) (1.4.2)\n",
      "Collecting regex>=2021.8.3 (from nltk)\n",
      "  Downloading https://mirrors.aliyun.com/pypi/packages/38/ec/ad2d7de49a600cdb8dd78434a1aeffe28b9d6fc42eb36afab4a27ad23384/regex-2024.11.6-cp312-cp312-win_amd64.whl (273 kB)\n",
      "     ---------------------------------------- 0.0/273.6 kB ? eta -:--:--\n",
      "     ---------- ---------------------------- 71.7/273.6 kB 2.0 MB/s eta 0:00:01\n",
      "     ------------------------ ------------- 174.1/273.6 kB 1.7 MB/s eta 0:00:01\n",
      "     ----------------------------------- -- 256.0/273.6 kB 2.0 MB/s eta 0:00:01\n",
      "     -------------------------------------- 273.6/273.6 kB 1.7 MB/s eta 0:00:00\n",
      "Requirement already satisfied: tqdm in c:\\users\\29470\\appdata\\local\\programs\\python\\python312\\lib\\site-packages (from nltk) (4.67.1)\n",
      "Requirement already satisfied: colorama in c:\\users\\29470\\appdata\\local\\programs\\python\\python312\\lib\\site-packages (from click->nltk) (0.4.6)\n",
      "Installing collected packages: regex, click, nltk\n",
      "Successfully installed click-8.1.8 nltk-3.9.1 regex-2024.11.6\n",
      "Note: you may need to restart the kernel to use updated packages.\n"
     ]
    },
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "\n",
      "[notice] A new release of pip is available: 24.0 -> 25.0\n",
      "[notice] To update, run: python.exe -m pip install --upgrade pip\n"
     ]
    }
   ],
   "execution_count": null
  },
  {
   "metadata": {
    "id": "n1a0V7-KTswK",
    "colab": {
     "base_uri": "https://localhost:8080/"
    },
    "outputId": "2c664bab-0fe6-4622-a9a7-eb80a5099653"
   },
   "cell_type": "code",
   "outputs": [
    {
     "output_type": "stream",
     "name": "stderr",
     "text": [
      "<ipython-input-27-de929730308c>:3: FutureWarning: You are using `torch.load` with `weights_only=False` (the current default value), which uses the default pickle module implicitly. It is possible to construct malicious pickle data which will execute arbitrary code during unpickling (See https://github.com/pytorch/pytorch/blob/main/SECURITY.md#untrusted-models for more details). In a future release, the default value for `weights_only` will be flipped to `True`. This limits the functions that could be executed during unpickling. Arbitrary objects will no longer be allowed to be loaded via this mode unless they are explicitly allowlisted by the user via `torch.serialization.add_safe_globals`. We recommend you start setting `weights_only=True` for any use case where you don't have full control of the loaded file. Please open an issue on GitHub for any issues related to this experimental feature.\n",
      "  model.load_state_dict(torch.load(f\"./checkpoints/translate-seq2seq/best.ckpt\", map_location=\"cpu\"))\n"
     ]
    }
   ],
   "execution_count": 27,
   "source": [
    "# load checkpoints,如何上线\n",
    "model = Sequence2Sequence(len(src_word2idx), len(trg_word2idx))\n",
    "model.load_state_dict(torch.load(f\"./checkpoints/translate-seq2seq/best.ckpt\", map_location=\"cpu\"))\n",
    "\n",
    "class Translator:\n",
    "    def __init__(self, model, src_tokenizer, trg_tokenizer):\n",
    "        self.model = model\n",
    "        self.model.eval() # 切换到验证模式\n",
    "        self.src_tokenizer = src_tokenizer\n",
    "        self.trg_tokenizer = trg_tokenizer\n",
    "\n",
    "    def __call__(self, sentence):\n",
    "        sentence = preprocess_sentence(sentence) # 预处理句子，标点符号处理等\n",
    "        encoder_input, attn_mask = self.src_tokenizer.encode(\n",
    "            [sentence.split()],  # 二维列表\n",
    "            padding_first=True,\n",
    "            add_bos=True,\n",
    "            add_eos=True,\n",
    "            return_mask=True,\n",
    "            ) # 对输入进行编码，并返回encode_piadding_mask\n",
    "        encoder_input = torch.Tensor(encoder_input).to(dtype=torch.int64) # 转换成tensor\n",
    "\n",
    "        preds, scores = model.infer(encoder_input=encoder_input, attn_mask=attn_mask) # 预测\n",
    "\n",
    "        trg_sentence = self.trg_tokenizer.decode([preds], split=True, remove_eos=False)[0] # 通过tokenizer转换成文字\n",
    "\n",
    "        return \" \".join(trg_sentence[:-1]) # list转字符串"
   ]
  },
  {
   "cell_type": "code",
   "source": [
    "translator = Translator(model.cpu(), src_tokenizer, trg_tokenizer)\n",
    "sp_en = \"Hoy hace muy buen tiempo. Vine a la escuela para asistir a clases, pero no había nadie en la escuela. Tom se sentó a mi lado leyendo un libro, mientras yo jugaba videojuegos. Me sentí muy feliz y emocionado. Luego, el profesor entró en el aula y todos nos levantamos.\"\n",
    "sp = preprocess_sentence(sp_sentence)\n",
    "translator(sp)# ???"
   ],
   "metadata": {
    "colab": {
     "base_uri": "https://localhost:8080/",
     "height": 35
    },
    "id": "4zy_MM_aob1r",
    "outputId": "0736c972-afca-4cd8-9529-5ba4b87534fb"
   },
   "execution_count": 28,
   "outputs": [
    {
     "output_type": "execute_result",
     "data": {
      "text/plain": [
       "'may i borrow this book ?'"
      ],
      "application/vnd.google.colaboratory.intrinsic+json": {
       "type": "string"
      }
     },
     "metadata": {},
     "execution_count": 28
    }
   ]
  },
  {
   "cell_type": "code",
   "source": [
    "translator(u\"El sonido viaja muy rápidamente .\")  # Sound travels very quickly."
   ],
   "metadata": {
    "colab": {
     "base_uri": "https://localhost:8080/",
     "height": 35
    },
    "id": "eG9uyCH6pRD6",
    "outputId": "0eddd435-1ef8-43e8-81c3-9d654d1bde29"
   },
   "execution_count": 29,
   "outputs": [
    {
     "output_type": "execute_result",
     "data": {
      "text/plain": [
       "'the sound travels very quickly .'"
      ],
      "application/vnd.google.colaboratory.intrinsic+json": {
       "type": "string"
      }
     },
     "metadata": {},
     "execution_count": 29
    }
   ]
  },
  {
   "cell_type": "code",
   "source": [
    "translator(u\"Tom come con la mano izquierda, pero escribe con la derecha .\")  # Tom eats with his left hand, but he writes with his right."
   ],
   "metadata": {
    "colab": {
     "base_uri": "https://localhost:8080/",
     "height": 35
    },
    "id": "Z-6jrqjLpjuc",
    "outputId": "8f8880c0-8592-4eba-8bc1-f7c4c8d5e720"
   },
   "execution_count": 31,
   "outputs": [
    {
     "output_type": "execute_result",
     "data": {
      "text/plain": [
       "'tom eats with his left hand , but he writes with the right .'"
      ],
      "application/vnd.google.colaboratory.intrinsic+json": {
       "type": "string"
      }
     },
     "metadata": {},
     "execution_count": 31
    }
   ]
  },
  {
   "cell_type": "code",
   "source": [
    "translator(u\"Me gusta leer y también me gusta jugar videojuegos, pero el clima está bastante bien.\")  # 我喜欢读书也喜欢打游戏，但是天气还不错哦"
   ],
   "metadata": {
    "colab": {
     "base_uri": "https://localhost:8080/",
     "height": 35
    },
    "id": "oA0mB57yp357",
    "outputId": "be7c782d-f968-457d-cccc-c73be6e5aad0"
   },
   "execution_count": 32,
   "outputs": [
    {
     "output_type": "execute_result",
     "data": {
      "text/plain": [
       "'i like to read and also like it well .'"
      ],
      "application/vnd.google.colaboratory.intrinsic+json": {
       "type": "string"
      }
     },
     "metadata": {},
     "execution_count": 32
    }
   ]
  },
  {
   "metadata": {
    "id": "SzhlPsMqTswK",
    "colab": {
     "base_uri": "https://localhost:8080/"
    },
    "outputId": "73feb8bc-c088-4eec-f56d-89be12d49172"
   },
   "cell_type": "code",
   "outputs": [
    {
     "output_type": "stream",
     "name": "stdout",
     "text": [
      "<class 'list'>\n",
      "<class 'list'>\n",
      "<class 'list'>\n",
      "<class 'list'>\n",
      "<class 'list'>\n",
      "<class 'list'>\n",
      "<class 'list'>\n",
      "<class 'list'>\n",
      "<class 'list'>\n",
      "<class 'list'>\n"
     ]
    }
   ],
   "execution_count": 37,
   "source": [
    "from nltk.translate.bleu_score import sentence_bleu\n",
    "def evaluate_bleu_on_test_set(test_data, translator):\n",
    "    \"\"\"\n",
    "    在测试集上计算平均BLEU分数\n",
    "    :param test_data: 测试及数据，格式为[(src_sentence1, ref_translation1),...]\n",
    "    :param translator: 翻译器对象\n",
    "    :return: 平均BLEU分数\n",
    "    \"\"\"\n",
    "    total_bleu = 0.0\n",
    "    num_samples = len(test_data)\n",
    "    i=0\n",
    "    for src_sentence, ref_translations in test_data:\n",
    "        # 翻译\n",
    "        candidate_translations = translator(src_sentence)\n",
    "      \n",
    "        # 计算BLEU分数\n",
    "        bleu_score = sentence_bleu([ref_translations.split()], candidate_translations.split(), weights=(1,0,0,0))\n",
    "        total_bleu += bleu_score\n",
    "\n",
    "        # 打印当前句子的BLEU分数\n",
    "        # print(f'Source: {src_sentence}')\n",
    "        # print(f'Reference: {\" \".join(ref_translations)}')\n",
    "        # print(f'Candidate: {candidate_translations}')\n",
    "        # print(f'BLEU score: {bleu_score:.4f}')\n",
    "        # print('-'*50)\n",
    "        # i+=1\n",
    "        # if i >10:\n",
    "        #     break\n",
    "\n",
    "    # 计算平均BLEU分数\n",
    "    avg_bleu = total_bleu/num_samples\n",
    "    return avg_bleu\n",
    "translator = Translator(model.cpu(), src_tokenizer, trg_tokenizer)\n",
    "test_data = [\n",
    "    (\"Hoy hace muy buen tiempo.\", \"The weather is very nice today.\"),\n",
    "    (\"Me gusta leer y también me gusta jugar videojuegos.\", \"I like to read and I also like to play video games.\"),\n",
    "    (\"El gato está durmiendo en el sofá.\", \"The cat is sleeping on the sofa.\"),\n",
    "    (\"Voy a la escuela todos los días.\", \"I go to school every day.\"),\n",
    "    (\"La comida en este restaurante es deliciosa.\", \"The food at this restaurant is delicious.\"),\n",
    "    (\"¿Cómo te llamas?\", \"What is your name?\"),\n",
    "    (\"La película que vimos anoche fue increíble.\", \"The movie we watched last night was amazing.\")]\n",
    "evaluate_bleu_on_test_set(test_data, translator)\n",
    "# evaluate_bleu_on_test_set(test_data, translator)"
   ]
  },
  {
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-02-07T04:20:27.921003Z",
     "start_time": "2025-02-07T04:20:27.916003Z"
    }
   },
   "cell_type": "code",
   "source": [
    "\n",
    "]\n",
    "for src_sentence, ref_translations in test_data:\n",
    "  print(type(ref_translations))\n",
    "  \n"
   ],
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "<class 'str'>\n",
      "<class 'str'>\n",
      "<class 'str'>\n",
      "<class 'str'>\n",
      "<class 'str'>\n",
      "<class 'str'>\n",
      "<class 'str'>\n"
     ]
    }
   ],
   "execution_count": 1
  }
 ],
 "metadata": {
  "kernelspec": {
   "name": "python3",
   "language": "python",
   "display_name": "Python 3 (ipykernel)"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.10.8"
  },
  "orig_nbformat": 4,
  "colab": {
   "provenance": [],
   "gpuType": "T4"
  },
  "accelerator": "GPU",
  "widgets": {
   "application/vnd.jupyter.widget-state+json": {
    "e712e832ad1541898150fac1428604ca": {
     "model_module": "@jupyter-widgets/controls",
     "model_name": "HBoxModel",
     "model_module_version": "1.5.0",
     "state": {
      "_dom_classes": [],
      "_model_module": "@jupyter-widgets/controls",
      "_model_module_version": "1.5.0",
      "_model_name": "HBoxModel",
      "_view_count": null,
      "_view_module": "@jupyter-widgets/controls",
      "_view_module_version": "1.5.0",
      "_view_name": "HBoxView",
      "box_style": "",
      "children": [
       "IPY_MODEL_c423f66222834a59b24e6c30c580d33f",
       "IPY_MODEL_b3d1f36c71f54549ad8fd99f50d289ed",
       "IPY_MODEL_212f179b111b4624869fbc461970c96c"
      ],
      "layout": "IPY_MODEL_9f65f3c20aab4fcaa85ba5995ac03ae0"
     }
    },
    "c423f66222834a59b24e6c30c580d33f": {
     "model_module": "@jupyter-widgets/controls",
     "model_name": "HTMLModel",
     "model_module_version": "1.5.0",
     "state": {
      "_dom_classes": [],
      "_model_module": "@jupyter-widgets/controls",
      "_model_module_version": "1.5.0",
      "_model_name": "HTMLModel",
      "_view_count": null,
      "_view_module": "@jupyter-widgets/controls",
      "_view_module_version": "1.5.0",
      "_view_name": "HTMLView",
      "description": "",
      "description_tooltip": null,
      "layout": "IPY_MODEL_2cbece938db9461fb678ad895c153ff9",
      "placeholder": "​",
      "style": "IPY_MODEL_5d659ce3b72a4eb6900747882b99e51b",
      "value": " 27%"
     }
    },
    "b3d1f36c71f54549ad8fd99f50d289ed": {
     "model_module": "@jupyter-widgets/controls",
     "model_name": "FloatProgressModel",
     "model_module_version": "1.5.0",
     "state": {
      "_dom_classes": [],
      "_model_module": "@jupyter-widgets/controls",
      "_model_module_version": "1.5.0",
      "_model_name": "FloatProgressModel",
      "_view_count": null,
      "_view_module": "@jupyter-widgets/controls",
      "_view_module_version": "1.5.0",
      "_view_name": "ProgressView",
      "bar_style": "danger",
      "description": "",
      "description_tooltip": null,
      "layout": "IPY_MODEL_67992ee2008249d29616b3c54c5d1bec",
      "max": 33500,
      "min": 0,
      "orientation": "horizontal",
      "style": "IPY_MODEL_67d524c40438453cb5cdae6f7bcd5543",
      "value": 9199
     }
    },
    "212f179b111b4624869fbc461970c96c": {
     "model_module": "@jupyter-widgets/controls",
     "model_name": "HTMLModel",
     "model_module_version": "1.5.0",
     "state": {
      "_dom_classes": [],
      "_model_module": "@jupyter-widgets/controls",
      "_model_module_version": "1.5.0",
      "_model_name": "HTMLModel",
      "_view_count": null,
      "_view_module": "@jupyter-widgets/controls",
      "_view_module_version": "1.5.0",
      "_view_name": "HTMLView",
      "description": "",
      "description_tooltip": null,
      "layout": "IPY_MODEL_146cc90654c8488ab4a96bc81677b06c",
      "placeholder": "​",
      "style": "IPY_MODEL_7dc93ff1d91e456a984ddcc2f483f7b2",
      "value": " 9199/33500 [25:37&lt;1:03:59,  6.33it/s, epoch=4, loss=0.891, val_loss=1.24]"
     }
    },
    "9f65f3c20aab4fcaa85ba5995ac03ae0": {
     "model_module": "@jupyter-widgets/base",
     "model_name": "LayoutModel",
     "model_module_version": "1.2.0",
     "state": {
      "_model_module": "@jupyter-widgets/base",
      "_model_module_version": "1.2.0",
      "_model_name": "LayoutModel",
      "_view_count": null,
      "_view_module": "@jupyter-widgets/base",
      "_view_module_version": "1.2.0",
      "_view_name": "LayoutView",
      "align_content": null,
      "align_items": null,
      "align_self": null,
      "border": null,
      "bottom": null,
      "display": null,
      "flex": null,
      "flex_flow": null,
      "grid_area": null,
      "grid_auto_columns": null,
      "grid_auto_flow": null,
      "grid_auto_rows": null,
      "grid_column": null,
      "grid_gap": null,
      "grid_row": null,
      "grid_template_areas": null,
      "grid_template_columns": null,
      "grid_template_rows": null,
      "height": null,
      "justify_content": null,
      "justify_items": null,
      "left": null,
      "margin": null,
      "max_height": null,
      "max_width": null,
      "min_height": null,
      "min_width": null,
      "object_fit": null,
      "object_position": null,
      "order": null,
      "overflow": null,
      "overflow_x": null,
      "overflow_y": null,
      "padding": null,
      "right": null,
      "top": null,
      "visibility": null,
      "width": null
     }
    },
    "2cbece938db9461fb678ad895c153ff9": {
     "model_module": "@jupyter-widgets/base",
     "model_name": "LayoutModel",
     "model_module_version": "1.2.0",
     "state": {
      "_model_module": "@jupyter-widgets/base",
      "_model_module_version": "1.2.0",
      "_model_name": "LayoutModel",
      "_view_count": null,
      "_view_module": "@jupyter-widgets/base",
      "_view_module_version": "1.2.0",
      "_view_name": "LayoutView",
      "align_content": null,
      "align_items": null,
      "align_self": null,
      "border": null,
      "bottom": null,
      "display": null,
      "flex": null,
      "flex_flow": null,
      "grid_area": null,
      "grid_auto_columns": null,
      "grid_auto_flow": null,
      "grid_auto_rows": null,
      "grid_column": null,
      "grid_gap": null,
      "grid_row": null,
      "grid_template_areas": null,
      "grid_template_columns": null,
      "grid_template_rows": null,
      "height": null,
      "justify_content": null,
      "justify_items": null,
      "left": null,
      "margin": null,
      "max_height": null,
      "max_width": null,
      "min_height": null,
      "min_width": null,
      "object_fit": null,
      "object_position": null,
      "order": null,
      "overflow": null,
      "overflow_x": null,
      "overflow_y": null,
      "padding": null,
      "right": null,
      "top": null,
      "visibility": null,
      "width": null
     }
    },
    "5d659ce3b72a4eb6900747882b99e51b": {
     "model_module": "@jupyter-widgets/controls",
     "model_name": "DescriptionStyleModel",
     "model_module_version": "1.5.0",
     "state": {
      "_model_module": "@jupyter-widgets/controls",
      "_model_module_version": "1.5.0",
      "_model_name": "DescriptionStyleModel",
      "_view_count": null,
      "_view_module": "@jupyter-widgets/base",
      "_view_module_version": "1.2.0",
      "_view_name": "StyleView",
      "description_width": ""
     }
    },
    "67992ee2008249d29616b3c54c5d1bec": {
     "model_module": "@jupyter-widgets/base",
     "model_name": "LayoutModel",
     "model_module_version": "1.2.0",
     "state": {
      "_model_module": "@jupyter-widgets/base",
      "_model_module_version": "1.2.0",
      "_model_name": "LayoutModel",
      "_view_count": null,
      "_view_module": "@jupyter-widgets/base",
      "_view_module_version": "1.2.0",
      "_view_name": "LayoutView",
      "align_content": null,
      "align_items": null,
      "align_self": null,
      "border": null,
      "bottom": null,
      "display": null,
      "flex": null,
      "flex_flow": null,
      "grid_area": null,
      "grid_auto_columns": null,
      "grid_auto_flow": null,
      "grid_auto_rows": null,
      "grid_column": null,
      "grid_gap": null,
      "grid_row": null,
      "grid_template_areas": null,
      "grid_template_columns": null,
      "grid_template_rows": null,
      "height": null,
      "justify_content": null,
      "justify_items": null,
      "left": null,
      "margin": null,
      "max_height": null,
      "max_width": null,
      "min_height": null,
      "min_width": null,
      "object_fit": null,
      "object_position": null,
      "order": null,
      "overflow": null,
      "overflow_x": null,
      "overflow_y": null,
      "padding": null,
      "right": null,
      "top": null,
      "visibility": null,
      "width": null
     }
    },
    "67d524c40438453cb5cdae6f7bcd5543": {
     "model_module": "@jupyter-widgets/controls",
     "model_name": "ProgressStyleModel",
     "model_module_version": "1.5.0",
     "state": {
      "_model_module": "@jupyter-widgets/controls",
      "_model_module_version": "1.5.0",
      "_model_name": "ProgressStyleModel",
      "_view_count": null,
      "_view_module": "@jupyter-widgets/base",
      "_view_module_version": "1.2.0",
      "_view_name": "StyleView",
      "bar_color": null,
      "description_width": ""
     }
    },
    "146cc90654c8488ab4a96bc81677b06c": {
     "model_module": "@jupyter-widgets/base",
     "model_name": "LayoutModel",
     "model_module_version": "1.2.0",
     "state": {
      "_model_module": "@jupyter-widgets/base",
      "_model_module_version": "1.2.0",
      "_model_name": "LayoutModel",
      "_view_count": null,
      "_view_module": "@jupyter-widgets/base",
      "_view_module_version": "1.2.0",
      "_view_name": "LayoutView",
      "align_content": null,
      "align_items": null,
      "align_self": null,
      "border": null,
      "bottom": null,
      "display": null,
      "flex": null,
      "flex_flow": null,
      "grid_area": null,
      "grid_auto_columns": null,
      "grid_auto_flow": null,
      "grid_auto_rows": null,
      "grid_column": null,
      "grid_gap": null,
      "grid_row": null,
      "grid_template_areas": null,
      "grid_template_columns": null,
      "grid_template_rows": null,
      "height": null,
      "justify_content": null,
      "justify_items": null,
      "left": null,
      "margin": null,
      "max_height": null,
      "max_width": null,
      "min_height": null,
      "min_width": null,
      "object_fit": null,
      "object_position": null,
      "order": null,
      "overflow": null,
      "overflow_x": null,
      "overflow_y": null,
      "padding": null,
      "right": null,
      "top": null,
      "visibility": null,
      "width": null
     }
    },
    "7dc93ff1d91e456a984ddcc2f483f7b2": {
     "model_module": "@jupyter-widgets/controls",
     "model_name": "DescriptionStyleModel",
     "model_module_version": "1.5.0",
     "state": {
      "_model_module": "@jupyter-widgets/controls",
      "_model_module_version": "1.5.0",
      "_model_name": "DescriptionStyleModel",
      "_view_count": null,
      "_view_module": "@jupyter-widgets/base",
      "_view_module_version": "1.2.0",
      "_view_name": "StyleView",
      "description_width": ""
     }
    }
   }
  }
 },
 "nbformat": 4,
 "nbformat_minor": 0
}
